text:
### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ a08ba54c-b0ec-4aa8-b4ba-f289a1e8a8a5
begin
    using Images, FileIO, PlutoUI, Plots
end

# ╔═╡ b389844f-72ab-47b8-bab8-f2d5c23253bc
md"""
# Basic image handling with Images.jl
"""

# ╔═╡ f68b8caf-a7b8-4fb5-8cef-f2fa697a9f22
md"""
This notebook shows some operations you can perform on images. We download an image from the net, inspect its data type, make the image grayscale, split the image to RGB components, gamma adjust the image, and display the RGB histogram.

This is the first part of a series of blog posts illustrating tasks you may want to do with Julia. I've added some explanations of what the code is doing, and I hope the result is readable with general knowledge of programming languages like Python.

I recommend finding a way to run the Pluto notebook(s) interactively. Hopefully at some point in the near future this is possible directly on the web, but before that happens, you may need to download the notebook, and run Julia on your laptop.

The most recent version of this notebook is in [this GitHub repo](https://github.com/StatisticalMice/julia-tutorials/blob/main/basic-image-handling.jl).

`Images.jl` is an image processing package that's written in Julia.

`FileIO.jl` provides file loading (and saving). It will try to infer the file's type, and to find a package that's capable of loading the file.

`PlutoUI.jl` add Pluto UI components. In this notebook it's used to add a gamma correction slider.

`Plots.jl` is one of the main plotting packages. It's used to display an RGB histogram of the gamma corrected image.

This notebook is done with `Pluto.jl`.
"""

# ╔═╡ f679ddee-7646-41c2-a56e-30b27d89a5cc
md"""
We'll download this example image by @thiszun from Pexels:

[Woman Wearing White Floral Off Shoulder Top](https://www.pexels.com/photo/woman-wearing-white-floral-off-shoulder-top-3653167/).
"""

# ╔═╡ 47fc092d-a639-4433-b6a2-084cd48ebb86
imageurl_with_extra = "https://images.pexels.com/photos/3653167/pexels-photo-3653167.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260"

# ╔═╡ 28a4deb6-0b0b-4e03-9ddc-83c8e5be179c
md"""
The URL has extra parts starting with `?`, which we remove.

The code takes the first element of the array returned by `split`, which is at index `1`.

It then converts the type `SubString{String}` to type `String` so that download will work, without this there will be an error.
"""

# ╔═╡ e23fb803-328a-4205-8cd3-8132e67e3dc8
imageurl = String(split(imageurl_with_extra, "?")[1])

# ╔═╡ b98152a1-fb56-4616-982d-b79699534d46
imagepath = download(imageurl)

# ╔═╡ 4ffc0f24-904a-4906-bdc1-12ae434ce43b
img = load(imagepath)

# ╔═╡ 9dad94a8-0629-4655-b097-f045a9ab0eaa
size(img)

# ╔═╡ a1296b34-b033-412c-be10-7ca88faa3fdc
head = img[700:2000, 600:1600]

# ╔═╡ 8d9c4fbf-11c7-420b-8aea-c9f50ef204aa
size(img)

# ╔═╡ 183f1ee9-7032-4ab0-b73d-d94c75a4c27b
typeof(img)

# ╔═╡ 12ddb2c5-85a6-4419-bc6a-5ec050f97a7e
md"""
The image is represented as a two dimensional Array of RGB numbers.

"The `N0f8` type (aliased to `Normed{UInt8,8}`) is represented internally by a `UInt8`, and makes `0x00` equivalent to `0.0` and `0xff` to `1.0`," according to `FixedPointNumbers.jl`.

In other words, each of the RGB components of the pixels is an 8-bit unsigned integer that represents a floating point number between 0.0 and 1.0.
"""

# ╔═╡ 2ce72842-b7ac-4ad7-8527-9793c45d83a7
let
	gray_image = Gray.(head)
	mosaicview(head, gray_image; nrow = 1)
end

# ╔═╡ c1038010-6f92-4fd5-a5fd-a5578a39b6f5
md"""
`Gray.(head)` applies the function `Gray()` to each of the pixels in `head`. This is called broadcasting. Without the dot, e.g. `Gray(head)`, Julia would try to find a function that takes as the input the whole image.

`mosaicview(head, gray_image; nrow = 1)` calls the function `mosaicview()` with two normal parameters, and one named parameter, e.g. `nrow = 1`. The named parameters are separated with the `;` character.
"""

# ╔═╡ c2e53aa1-e91f-469e-848c-67bebecaa444
mosaicview(
	(x->RGB(x, 0, 0)).(red.(head)),
	(x->RGB(0, x, 0)).(green.(head)),
	(x->RGB(0, 0, x)).(blue.(head)) ; nrow = 1)

# ╔═╡ 60fbe058-64c8-4222-b140-6a7c2ebf0a17
md"""
`(x->RGB(x, 0, 0)).(red.(head))` is read as follows:

`x->RGB(x, 0, 0)` defines an anonymous function that takes the parameter `x` and returns the RGB color where the first parameter, red, is `x`, and the rest of the colors are `0`.

`red.(head)` returns the red component of `head` pixels using broadcasting.

`(x->RGB(x, 0, 0)).(red.(head))` applies the anonymous function with broadcasting to the red values of the all the pixels. The result is the red component of the image.
"""

# ╔═╡ 79635ec3-44ec-4fdb-ab88-659cc8911877
function invert(color::AbstractRGB)
	return RGB(1.0-red(color),1.0-green(color),1.0-blue(color))
end

# ╔═╡ 5d0ae940-2509-498d-a8ab-404c03587b0f
md"""
As `red(color)` is a floating point number, its inverted color is `1.0-red(color)`.
"""

# ╔═╡ 9b5bccea-2f25-4c50-a69a-0e86aa061c45
invert.(img)

# ╔═╡ c7c3827b-7d57-4e1e-bccb-bb4074ef8c43
typeof(invert.(img))

# ╔═╡ 4829f153-98c8-4765-a031-d22646f8ab7a
md"""
Note that Julia converted each pixel's color value to `Float64` in this case.
"""

# ╔═╡ e5c8131d-2215-4d01-ade6-c63b50cb2c80
@bind gamma_correction Slider(-2.0:0.5:3.0, show_value=true, default = -1.0)
# The default is -1.0 so that the notebook shows a difference to the original image.
# In the interactive version of this you can drag the slider.

# ╔═╡ f9f7c010-a1f1-4569-941a-e10281d27746
function gamma_adjusted(image)
	adjust_histogram(image, GammaCorrection(gamma = exp(gamma_correction)))
end

# ╔═╡ 86a81dca-68eb-4085-80c2-436e4e55ef6f
img_gamma_ajusted = gamma_adjusted(img)

# ╔═╡ e6de7425-3353-4873-8cf5-14b689f2ed19
md"""
Pluto/HTML doesn't support a slider where the slider's value would be exponentially rising. Therefore, the slider has the range `-2.0:0.5:3.0`, and it's used in gamma correction as `GammaCorrection(gamma = exp(gamma_correction))`.

"""

# ╔═╡ dfb78be2-f7b0-48c8-a81e-08d668034c48
collect(-2.0:0.5:3.0)

# ╔═╡ ab5b1cbc-0048-490d-9306-9f0432884696
md"""
The range expression represent a list of values starting at `-2.0`, incrementing by `0.5`, and ending at `3.0`.
"""

# ╔═╡ 45889ab1-d1c4-4259-ae34-9c00fa4f5be0
function image_histogram(image)
	_, reds = build_histogram(red.(image), 64; minval=0, maxval=1)
	_, greens = build_histogram(green.(image), 64; minval=0, maxval=1)
	_, blues = build_histogram(blue.(image), 64; minval=0, maxval=1)
	plot(reds, linecolor = :red, legend = false)
	plot!(greens, linecolor = :green, legend = false)
	plot!(blues, linecolor = :blue, legend = false)
end

# ╔═╡ 012fbd20-9d25-4d56-a5a8-c2f839b7b517
image_histogram(img_gamma_ajusted)

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
FileIO = "~1.10.1"
Images = "~0.24.1"
Plots = "~1.19.2"
PlutoUI = "~0.7.9"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[AbstractFFTs]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "485ee0867925449198280d4af84bdb46a2a404d0"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.0.1"

[[Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[ArrayInterface]]
deps = ["IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"]
git-tree-sha1 = "045ff5e1bc8c6fb1ecb28694abba0a0d55b5f4f5"
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
version = "3.1.17"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[AxisAlgorithms]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
git-tree-sha1 = "a4d07a1c313392a77042855df46c5f534076fab9"
uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950"
version = "1.0.0"

[[AxisArrays]]
deps = ["Dates", "IntervalSets", "IterTools", "RangeArrays"]
git-tree-sha1 = "f31f50712cbdf40ee8287f0443b57503e34122ef"
uuid = "39de3d68-74b9-583c-8d2d-e117c070f3a9"
version = "0.4.3"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c3598e525718abcc440f69cc6d5f60dda0a1b61e"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.6+5"

[[CEnum]]
git-tree-sha1 = "215a9aa4a1f23fbd05b92769fdd62559488d70e9"
uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
version = "0.4.1"

[[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 = "e2f47f6d8337369411569fd45ae5753ca10394c6"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.0+6"

[[CatIndices]]
deps = ["CustomUnitRanges", "OffsetArrays"]
git-tree-sha1 = "a0f80a09780eed9b1d106a1bf62041c2efc995bc"
uuid = "aafaddc9-749c-510e-ac4f-586e18779b91"
version = "0.2.2"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "0b0aa9d61456940511416b59a0e902c57b154956"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "0.10.12"

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random", "StaticArrays"]
git-tree-sha1 = "ed268efe58512df8c7e224d2e170afd76dd6a417"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.13.0"

[[ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[ColorVectorSpace]]
deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "SpecialFunctions", "Statistics", "TensorCore"]
git-tree-sha1 = "42a9b08d3f2f951c9b283ea427d96ed9f1f30343"
uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
version = "0.9.5"

[[Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[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 = "dc7dedc2c2aa9faf59a55c622760a25cbefbe941"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.31.0"

[[CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[ComputationalResources]]
git-tree-sha1 = "52cb3ec90e8a8bea0e62e275ba577ad0f74821f7"
uuid = "ed09eef8-17a6-5b46-8889-db040fac31e3"
version = "0.3.2"

[[Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"

[[CoordinateTransformations]]
deps = ["LinearAlgebra", "StaticArrays"]
git-tree-sha1 = "6d1c23e740a586955645500bbec662476204a52c"
uuid = "150eb455-5306-5404-9cee-2592286d6298"
version = "0.6.1"

[[CustomUnitRanges]]
git-tree-sha1 = "537c988076d001469093945f3bd0b300b8d3a7f3"
uuid = "dc8bdbbb-1ca9-579f-8c36-e416f6a65cce"
version = "1.0.1"

[[DataAPI]]
git-tree-sha1 = "ee400abb2298bd13bfc3df1c412ed228061a2385"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.7.0"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "4437b64df1e0adccc3e5d1adbc3ac741095e4677"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.9"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distances]]
deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
git-tree-sha1 = "abe4ad222b26af3337262b8afb28fab8d215e9f8"
uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
version = "0.10.3"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.5"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "92d8f9f208637e8d2d28c664051a00569c01493d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.1.5+1"

[[EllipsisNotation]]
deps = ["ArrayInterface"]
git-tree-sha1 = "8041575f021cba5a099a456b4163c9a08b566a02"
uuid = "da5c29d0-fa7d-589e-88eb-ea29b0a81949"
version = "1.1.0"

[[Expat_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b3bfd02e98aedfa5cf885665493c5598c350cd2f"
uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
version = "2.2.10+0"

[[FFMPEG]]
deps = ["FFMPEG_jll"]
git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
version = "0.4.1"

[[FFMPEG_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "LibVPX_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 = "3cc57ad0a213808473eafef4845a74766242e05f"
uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "4.3.1+4"

[[FFTViews]]
deps = ["CustomUnitRanges", "FFTW"]
git-tree-sha1 = "70a0cfd9b1c86b0209e38fbfe6d8231fd606eeaf"
uuid = "4f61f5a4-77b1-5117-aa51-3ab5ef4ef0cd"
version = "0.3.1"

[[FFTW]]
deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
git-tree-sha1 = "f985af3b9f4e278b1d24434cbb546d6092fca661"
uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
version = "1.4.3"

[[FFTW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3676abafff7e4ff07bbd2c42b3d8201f31653dcc"
uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
version = "3.3.9+8"

[[FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "256d8e6188f3f1ebfa1a5d17e072a0efafa8c5bf"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.10.1"

[[FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[Fontconfig_jll]]
deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "35895cf184ceaab11fd778b4590144034a167a2f"
uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
version = "2.13.1+14"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "cbd58c9deb1d304f5a245a0b7eb841a2560cfec6"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.1+5"

[[FriBidi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91"
uuid = "559328eb-81f9-559d-9380-de523a88c83c"
version = "1.0.10+0"

[[GLFW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll"]
git-tree-sha1 = "dba1e8614e98949abfa60480b13653813d8f0157"
uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89"
version = "3.3.5+0"

[[GR]]
deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "Serialization", "Sockets", "Test", "UUIDs"]
git-tree-sha1 = "9f473cdf6e2eb360c576f9822e7c765dd9d26dbc"
uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
version = "0.58.0"

[[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 = "eaf96e05a880f3db5ded5a5a8a7817ecba3c7392"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.58.0+0"

[[GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "15ff9a14b9e1218958d3530cc288cf31465d9ae2"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.3.13"

[[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"

[[Glib_jll]]
deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "47ce50b742921377301e15005c96e979574e130b"
uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
version = "2.68.1+0"

[[Graphics]]
deps = ["Colors", "LinearAlgebra", "NaNMath"]
git-tree-sha1 = "2c1cf4df419938ece72de17f368a021ee162762e"
uuid = "a2bd30eb-e257-5431-a919-1863eab51364"
version = "1.1.0"

[[Grisu]]
git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
version = "1.0.2"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "c6a1fff2fd4b1da29d3dccaffb1e1001244d844e"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.12"

[[IdentityRanges]]
deps = ["OffsetArrays"]
git-tree-sha1 = "be8fcd695c4da16a1d6d0cd213cb88090a150e3b"
uuid = "bbac6d45-d8f3-5730-bfe4-7a449cd117ca"
version = "0.3.1"

[[IfElse]]
git-tree-sha1 = "28e837ff3e7a6c3cdb252ce49fb412c8eb3caeef"
uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
version = "0.1.0"

[[ImageAxes]]
deps = ["AxisArrays", "ImageCore", "Reexport", "SimpleTraits"]
git-tree-sha1 = "794ad1d922c432082bc1aaa9fa8ffbd1fe74e621"
uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac"
version = "0.6.9"

[[ImageContrastAdjustment]]
deps = ["ColorVectorSpace", "ImageCore", "ImageTransformations", "Parameters"]
git-tree-sha1 = "2e6084db6cccab11fe0bc3e4130bd3d117092ed9"
uuid = "f332f351-ec65-5f6a-b3d1-319c6670881a"
version = "0.3.7"

[[ImageCore]]
deps = ["AbstractFFTs", "Colors", "FixedPointNumbers", "Graphics", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "Reexport"]
git-tree-sha1 = "db645f20b59f060d8cfae696bc9538d13fd86416"
uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
version = "0.8.22"

[[ImageDistances]]
deps = ["ColorVectorSpace", "Distances", "ImageCore", "ImageMorphology", "LinearAlgebra", "Statistics"]
git-tree-sha1 = "6378c34a3c3a216235210d19b9f495ecfff2f85f"
uuid = "51556ac3-7006-55f5-8cb3-34580c88182d"
version = "0.2.13"

[[ImageFiltering]]
deps = ["CatIndices", "ColorVectorSpace", "ComputationalResources", "DataStructures", "FFTViews", "FFTW", "ImageCore", "LinearAlgebra", "OffsetArrays", "Requires", "SparseArrays", "StaticArrays", "Statistics", "TiledIteration"]
git-tree-sha1 = "bf96839133212d3eff4a1c3a80c57abc7cfbf0ce"
uuid = "6a3955dd-da59-5b1f-98d4-e7296123deb5"
version = "0.6.21"

[[ImageIO]]
deps = ["FileIO", "Netpbm", "PNGFiles", "TiffImages", "UUIDs"]
git-tree-sha1 = "d067570b4d4870a942b19d9ceacaea4fb39b69a1"
uuid = "82e4d734-157c-48bb-816b-45c225c6df19"
version = "0.5.6"

[[ImageMagick]]
deps = ["FileIO", "ImageCore", "ImageMagick_jll", "InteractiveUtils", "Libdl", "Pkg", "Random"]
git-tree-sha1 = "5bc1cb62e0c5f1005868358db0692c994c3a13c6"
uuid = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
version = "1.2.1"

[[ImageMagick_jll]]
deps = ["JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "1c0a2295cca535fabaf2029062912591e9b61987"
uuid = "c73af94c-d91f-53ed-93a7-00f77d67a9d7"
version = "6.9.10-12+3"

[[ImageMetadata]]
deps = ["AxisArrays", "ColorVectorSpace", "ImageAxes", "ImageCore", "IndirectArrays"]
git-tree-sha1 = "ae76038347dc4edcdb06b541595268fca65b6a42"
uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
version = "0.9.5"

[[ImageMorphology]]
deps = ["ColorVectorSpace", "ImageCore", "LinearAlgebra", "TiledIteration"]
git-tree-sha1 = "68e7cbcd7dfaa3c2f74b0a8ab3066f5de8f2b71d"
uuid = "787d08f9-d448-5407-9aad-5290dd7ab264"
version = "0.2.11"

[[ImageQualityIndexes]]
deps = ["ColorVectorSpace", "ImageCore", "ImageDistances", "ImageFiltering", "OffsetArrays", "Statistics"]
git-tree-sha1 = "1198f85fa2481a3bb94bf937495ba1916f12b533"
uuid = "2996bd0c-7a13-11e9-2da2-2f5ce47296a9"
version = "0.2.2"

[[ImageShow]]
deps = ["Base64", "FileIO", "ImageCore", "OffsetArrays", "Requires", "StackViews"]
git-tree-sha1 = "832abfd709fa436a562db47fd8e81377f72b01f9"
uuid = "4e3cecfd-b093-5904-9786-8bbb286a6a31"
version = "0.3.1"

[[ImageTransformations]]
deps = ["AxisAlgorithms", "ColorVectorSpace", "CoordinateTransformations", "IdentityRanges", "ImageCore", "Interpolations", "OffsetArrays", "Rotations", "StaticArrays"]
git-tree-sha1 = "d966631de06f36c8cd4bec4bb2e8fa731db16ed9"
uuid = "02fcd773-0e25-5acc-982a-7f6622650795"
version = "0.8.12"

[[Images]]
deps = ["AxisArrays", "Base64", "ColorVectorSpace", "FileIO", "Graphics", "ImageAxes", "ImageContrastAdjustment", "ImageCore", "ImageDistances", "ImageFiltering", "ImageIO", "ImageMagick", "ImageMetadata", "ImageMorphology", "ImageQualityIndexes", "ImageShow", "ImageTransformations", "IndirectArrays", "OffsetArrays", "Random", "Reexport", "SparseArrays", "StaticArrays", "Statistics", "StatsBase", "TiledIteration"]
git-tree-sha1 = "8b714d5e11c91a0d945717430ec20f9251af4bd2"
uuid = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
version = "0.24.1"

[[IndirectArrays]]
git-tree-sha1 = "c2a145a145dc03a7620af1444e0264ef907bd44f"
uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959"
version = "0.5.1"

[[Inflate]]
git-tree-sha1 = "f5fc07d4e706b84f72d54eedcc1c13d92fb0871c"
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
version = "0.1.2"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

[[IntelOpenMP_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d979e54b71da82f3a65b62553da4fc3d18c9004c"
uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
version = "2018.0.3+2"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[Interpolations]]
deps = ["AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"]
git-tree-sha1 = "1470c80592cf1f0a35566ee5e93c5f8221ebc33a"
uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
version = "0.13.3"

[[IntervalSets]]
deps = ["Dates", "EllipsisNotation", "Statistics"]
git-tree-sha1 = "3cc368af3f110a767ac786560045dceddfc16758"
uuid = "8197267c-284f-5f27-9208-e0e47529a953"
version = "0.5.3"

[[IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.1"

[[JpegTurbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d735490ac75c5cb9f1b00d8b5509c11984dc6943"
uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
version = "2.1.0+0"

[[LAME_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c"
uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
version = "3.100.1+0"

[[LZO_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6"
uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
version = "2.10.1+0"

[[LaTeXStrings]]
git-tree-sha1 = "c7f1c695e06c01b95a67f0cd1d34994f3e7db104"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.2.1"

[[Latexify]]
deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"]
git-tree-sha1 = "a4b12a1bd2ebade87891ab7e36fdbce582301a92"
uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
version = "0.15.6"

[[LazyArtifacts]]
deps = ["Artifacts", "Pkg"]
uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[LibVPX_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "12ee7e23fa4d18361e7c2cde8f8337d4c3101bc7"
uuid = "dd192d2f-8180-539f-9fb4-cc70b1dcf69a"
version = "1.10.0+0"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[Libffi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "761a393aeccd6aa92ec3515e428c26bf99575b3b"
uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
version = "3.2.2+0"

[[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"

[[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"

[[Libgpg_error_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9"
uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
version = "1.42.0+0"

[[Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+1"

[[Libmount_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73"
uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
version = "2.35.0+0"

[[Libtiff_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"]
git-tree-sha1 = "340e257aada13f95f98ee352d316c3bed37c8ab9"
uuid = "89763e89-9b03-5906-acba-b20f662cd828"
version = "4.3.0+0"

[[Libuuid_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066"
uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
version = "2.36.0+0"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[LogExpFunctions]]
deps = ["DocStringExtensions", "LinearAlgebra"]
git-tree-sha1 = "7bd5f6565d80b6bf753738d2bc40a5dfea072070"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.2.5"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[MKL_jll]]
deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "Pkg"]
git-tree-sha1 = "c253236b0ed414624b083e6b72bfe891fbd2c7af"
uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
version = "2021.1.1+1"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "6a8a2a625ab0dea913aba95c11370589e0239ff0"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.6"

[[MappedArrays]]
git-tree-sha1 = "18d3584eebc861e311a552cbb67723af8edff5de"
uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
version = "0.4.0"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Measures]]
git-tree-sha1 = "e498ddeee6f9fdb4551ce855a46f54dbd900245f"
uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
version = "0.3.1"

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "4ea90bd5d3985ae1f9a908bd4500ae88921c5ce7"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.0"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[MosaicViews]]
deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"]
git-tree-sha1 = "b34e3bc3ca7c94914418637cb10cc4d1d80d877d"
uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389"
version = "0.3.3"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

[[Netpbm]]
deps = ["ColorVectorSpace", "FileIO", "ImageCore"]
git-tree-sha1 = "09589171688f0039f13ebe0fdcc7288f50228b52"
uuid = "f09324ee-3d7c-5217-9330-fc30815ba969"
version = "1.0.1"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[OffsetArrays]]
deps = ["Adapt"]
git-tree-sha1 = "4f825c6da64aebaa22cc058ecfceed1ab9af1c7e"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.10.3"

[[Ogg_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7937eda4681660b4d6aeeecc2f7e1c81c8ee4e2f"
uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
version = "1.3.5+0"

[[OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "15003dcb7d8db3c6c857fda14891a539a8f2705a"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.10+0"

[[OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[Opus_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
uuid = "91d4177d-7536-5919-b921-800302f37372"
version = "1.3.2+0"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[PCRE_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b2a7af664e098055a7529ad1a900ded962bca488"
uuid = "2f80f16e-611a-54ab-bc61-aa92de5b98fc"
version = "8.44.0+0"

[[PNGFiles]]
deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"]
git-tree-sha1 = "520e28d4026d16dcf7b8c8140a3041f0e20a9ca8"
uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
version = "0.3.7"

[[PaddedViews]]
deps = ["OffsetArrays"]
git-tree-sha1 = "0fa5e78929aebc3f6b56e1a88cf505bb00a354c4"
uuid = "5432bcbf-9aad-5242-b902-cca2824c8663"
version = "0.5.8"

[[Parameters]]
deps = ["OrderedCollections", "UnPack"]
git-tree-sha1 = "2276ac65f1e236e0a6ea70baff3f62ad4c625345"
uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a"
version = "0.12.2"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "1.1.0"

[[Pixman_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b4f5d02549a10e20780a24fce72bea96b6329e29"
uuid = "30392449-352a-5448-841d-b1acce4e97dc"
version = "0.40.1+0"

[[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"

[[PkgVersion]]
deps = ["Pkg"]
git-tree-sha1 = "a7a7e1a88853564e551e4eba8650f8c38df79b37"
uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
version = "0.1.1"

[[PlotThemes]]
deps = ["PlotUtils", "Requires", "Statistics"]
git-tree-sha1 = "a3a964ce9dc7898193536002a6dd892b1b5a6f1d"
uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
version = "2.0.1"

[[PlotUtils]]
deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"]
git-tree-sha1 = "501c20a63a34ac1d015d5304da0e645f42d91c9f"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.0.11"

[[Plots]]
deps = ["Base64", "Contour", "Dates", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs"]
git-tree-sha1 = "f3d4d35b8cb87adc844c05c722f505776ac29988"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.19.2"

[[PlutoUI]]
deps = ["Base64", "Dates", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "44e225d5837e2a2345e69a1d1e01ac2443ff9fcb"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.9"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[ProgressMeter]]
deps = ["Distributed", "Printf"]
git-tree-sha1 = "afadeba63d90ff223a6a48d2009434ecee2ec9e8"
uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
version = "1.7.1"

[[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 = "ad368663a5e20dbb8d6dc2fddeefe4dae0781ae8"
uuid = "ea2cea3b-5b76-57ae-a6ef-0a8af62496e1"
version = "5.15.3+0"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[RangeArrays]]
git-tree-sha1 = "b9039e93773ddcfc828f12aadf7115b4b4d225f5"
uuid = "b3c3ace0-ae52-54e7-9d0b-2c1406fd6b9d"
version = "0.3.2"

[[Ratios]]
git-tree-sha1 = "37d210f612d70f3f7d57d488cb3b6eff56ad4e41"
uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
version = "0.4.0"

[[RecipesBase]]
git-tree-sha1 = "b3fb709f3c97bfc6e948be68beeecb55a0b340ae"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.1"

[[RecipesPipeline]]
deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"]
git-tree-sha1 = "2a7a2469ed5d94a98dea0e85c46fa653d76be0cd"
uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c"
version = "0.3.4"

[[Reexport]]
git-tree-sha1 = "5f6c21241f0f655da3952fd60aa18477cf96c220"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.1.0"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[Rotations]]
deps = ["LinearAlgebra", "StaticArrays", "Statistics"]
git-tree-sha1 = "2ed8d8a16d703f900168822d83699b8c3c1a5cd8"
uuid = "6038ab10-8711-5258-84ad-4b1120ba62dc"
version = "1.0.2"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Scratch]]
deps = ["Dates"]
git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.1.0"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Showoff]]
deps = ["Dates", "Grisu"]
git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
version = "1.0.3"

[[SimpleTraits]]
deps = ["InteractiveUtils", "MacroTools"]
git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
version = "0.9.4"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[SpecialFunctions]]
deps = ["ChainRulesCore", "LogExpFunctions", "OpenSpecFun_jll"]
git-tree-sha1 = "a50550fa3164a8c46747e62063b4d774ac1bcf49"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.5.1"

[[StackViews]]
deps = ["OffsetArrays"]
git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c"
uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15"
version = "0.1.1"

[[Static]]
deps = ["IfElse"]
git-tree-sha1 = "2740ea27b66a41f9d213561a04573da5d3823d4b"
uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
version = "0.2.5"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "1b9a0f17ee0adde9e538227de093467348992397"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.7"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[StatsAPI]]
git-tree-sha1 = "1958272568dc176a1d881acb797beb909c785510"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.0.0"

[[StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "2f6792d523d7448bbe2fec99eca9218f06cc746d"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.8"

[[StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "000e168f5cc9aded17b6999a560b7c11dda69095"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.0"

[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "8ed4a3ea724dac32670b062be3ef1c1de6773ae8"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.4.4"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[TensorCore]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6"
uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50"
version = "0.1.1"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[TiffImages]]
deps = ["ColorTypes", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "OffsetArrays", "OrderedCollections", "PkgVersion", "ProgressMeter"]
git-tree-sha1 = "03fb246ac6e6b7cb7abac3b3302447d55b43270e"
uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
version = "0.4.1"

[[TiledIteration]]
deps = ["OffsetArrays"]
git-tree-sha1 = "52c5f816857bfb3291c7d25420b1f4aca0a74d18"
uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac"
version = "0.3.0"

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[UnPack]]
git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b"
uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
version = "1.0.2"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[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"

[[Wayland_protocols_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll"]
git-tree-sha1 = "2839f1c1296940218e35df0bbb220f2a79686670"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.18.0+4"

[[WoodburyMatrices]]
deps = ["LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "59e2ad8fd1591ea019a5259bd012d7aee15f995c"
uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6"
version = "0.5.3"

[[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"

[[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"

[[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"

[[Xorg_libXau_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4e490d5c960c314f33885790ed410ff3a94ce67e"
uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
version = "1.0.9+4"

[[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"

[[Xorg_libXdmcp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fe47bd2247248125c428978740e18a681372dd4"
uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
version = "1.1.3+4"

[[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"

[[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"

[[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"

[[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"

[[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"

[[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"

[[Xorg_libpthread_stubs_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "6783737e45d3c59a4a4c4091f5f88cdcf0908cbb"
uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
version = "0.1.0+3"

[[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"

[[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"

[[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"

[[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"

[[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"

[[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"

[[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"

[[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"

[[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"

[[Xorg_xtrans_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "79c31e7844f6ecf779705fbc12146eb190b7d845"
uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
version = "1.4.0+3"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "cc4bf3fdde8b7e3e9fa0351bdeedba1cf3b7f6e6"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.0+0"

[[libass_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "acc685bcf777b2202a904cdcb49ad34c2fa1880c"
uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
version = "0.14.0+4"

[[libfdk_aac_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7a5780a0d9c6864184b3a2eeeb833a0c871f00ab"
uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
version = "0.1.6+4"

[[libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+0"

[[libvorbis_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
git-tree-sha1 = "c45f4e40e7aafe9d086379e5578947ec8b95a8fb"
uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
version = "1.3.7+0"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"

[[x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d713c1ce4deac133e3334ee12f4adff07f81778f"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2020.7.14+2"

[[x265_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "487da2f8f2f0c8ee0e83f39d13037d6bbf0a45ab"
uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
version = "3.0.0+3"

[[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:
# ╟─b389844f-72ab-47b8-bab8-f2d5c23253bc
# ╟─f68b8caf-a7b8-4fb5-8cef-f2fa697a9f22
# ╠═a08ba54c-b0ec-4aa8-b4ba-f289a1e8a8a5
# ╟─f679ddee-7646-41c2-a56e-30b27d89a5cc
# ╠═47fc092d-a639-4433-b6a2-084cd48ebb86
# ╟─28a4deb6-0b0b-4e03-9ddc-83c8e5be179c
# ╠═e23fb803-328a-4205-8cd3-8132e67e3dc8
# ╠═b98152a1-fb56-4616-982d-b79699534d46
# ╠═4ffc0f24-904a-4906-bdc1-12ae434ce43b
# ╠═9dad94a8-0629-4655-b097-f045a9ab0eaa
# ╠═a1296b34-b033-412c-be10-7ca88faa3fdc
# ╠═8d9c4fbf-11c7-420b-8aea-c9f50ef204aa
# ╠═183f1ee9-7032-4ab0-b73d-d94c75a4c27b
# ╟─12ddb2c5-85a6-4419-bc6a-5ec050f97a7e
# ╠═2ce72842-b7ac-4ad7-8527-9793c45d83a7
# ╟─c1038010-6f92-4fd5-a5fd-a5578a39b6f5
# ╠═c2e53aa1-e91f-469e-848c-67bebecaa444
# ╟─60fbe058-64c8-4222-b140-6a7c2ebf0a17
# ╠═79635ec3-44ec-4fdb-ab88-659cc8911877
# ╟─5d0ae940-2509-498d-a8ab-404c03587b0f
# ╠═9b5bccea-2f25-4c50-a69a-0e86aa061c45
# ╠═c7c3827b-7d57-4e1e-bccb-bb4074ef8c43
# ╟─4829f153-98c8-4765-a031-d22646f8ab7a
# ╠═86a81dca-68eb-4085-80c2-436e4e55ef6f
# ╠═f9f7c010-a1f1-4569-941a-e10281d27746
# ╠═e5c8131d-2215-4d01-ade6-c63b50cb2c80
# ╠═012fbd20-9d25-4d56-a5a8-c2f839b7b517
# ╟─e6de7425-3353-4873-8cf5-14b689f2ed19
# ╠═dfb78be2-f7b0-48c8-a81e-08d668034c48
# ╟─ab5b1cbc-0048-490d-9306-9f0432884696
# ╠═45889ab1-d1c4-4259-ae34-9c00fa4f5be0
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002

max_stars_repo_name: StatisticalMice/julia-tutorials

max_issues_repo_name: StatisticalMice/julia-tutorials

max_forks_repo_name: StatisticalMice/julia-tutorials


text:
export node_neighbors, find_empty_nodes, pick_empty, has_empty_nodes, get_node_contents,
NodeIterator, space_neighbors, nodes, get_node_agents
export nv, ne
export GraphSpace, GridSpace

#######################################################################################
# Basic space definition
#######################################################################################
abstract type DiscreteSpace <: AbstractSpace end

LightGraphs.nv(space::DiscreteSpace) = LightGraphs.nv(space.graph)
LightGraphs.ne(space::DiscreteSpace) = LightGraphs.ne(space.graph)

"""
    nv(model::ABM)
Return the number of nodes (vertices) in the `model` space.
"""
LightGraphs.nv(abm::ABM) = LightGraphs.nv(abm.space.graph)

"""
    ne(model::ABM)
Return the number of edges in the `model` space.
"""
LightGraphs.ne(abm::ABM) = LightGraphs.ne(abm.space.graph)

struct GraphSpace{G} <: DiscreteSpace
  graph::G
  agent_positions::Vector{Vector{Int}}
end
struct GridSpace{G, D, I<:Integer} <: DiscreteSpace
  graph::G # Graph
  agent_positions::Vector{Vector{Int}}
  dimensions::NTuple{D, I}
end
function Base.show(io::IO, abm::DiscreteSpace)
    s = "$(nameof(typeof(abm))) with $(nv(abm)) nodes and $(ne(abm)) edges"
    print(io, s)
end

agent_positions(m::ABM) = m.space.agent_positions
agent_positions(m::DiscreteSpace) = m.agent_positions
Base.size(s::GridSpace) = s.dimensions

"""
    isempty(node::Int, model::ABM)
Return `true` if there are no agents in `node`.
"""
Base.isempty(node::Integer, model::ABM) =
isempty(model.space.agent_positions[node])

"""
    GraphSpace(graph::AbstractGraph)
Create a `GraphSpace` instance that is underlined by an arbitrary graph from
[LightGraphs.jl](https://github.com/JuliaGraphs/LightGraphs.jl).
In this case, your agent type must have a `pos` field that is of type `Int`.
"""
function GraphSpace(graph::G) where {G<:AbstractGraph}
  agent_positions = [Int[] for i in 1:LightGraphs.nv(graph)]
  return GraphSpace{G}(graph, agent_positions)
end

"""
    GridSpace(dims::NTuple; periodic = false, moore = false) → GridSpace
Create a `GridSpace` instance that represents a grid of dimensionality `length(dims)`,
with each dimension having the size of the corresponding entry of `dims`.
Such grids are typically used in cellular-automata-like models.
In this case, your agent type must have a `pos` field that is of type `NTuple{Int}`.

The two keyword arguments denote if the grid should be periodic on its ends,
and if the connections should be of type Moore or not (in the Moore case
the diagonal connections are also valid. E.g. for a 2D grid, each node has
8 neighbors).
"""
function GridSpace(dims::NTuple{D, I}; periodic = false, moore = false) where {D, I}
  graph = _grid(dims..., periodic, moore)
  agent_positions = [Int[] for i in 1:LightGraphs.nv(graph)]
  return GridSpace{typeof(graph), D, I}(graph, agent_positions, dims)
end

# Deprecation
@deprecate Space(graph::G) where {G<:AbstractGraph} GraphSpace(graph::G) where {G<:AbstractGraph}
@deprecate Space(dims::NTuple{D, I}) where {D, I} GridSpace(dims::NTuple{D, I}) where {D, I}

# 1d grid
function _grid(length::Integer, periodic::Bool=false, moore::Bool = false)
  g = LightGraphs.path_graph(length)
  if periodic
    add_edge!(g, 1, length)
  end
  return g
end

# 2d grid
function _grid(x::Integer, y::Integer, periodic::Bool = false, moore::Bool = false)
  if moore
    g = _grid2d_moore(x, y, periodic)
  else
    g = LightGraphs.grid([x, y], periodic=periodic)
  end
  return g
end

function _grid2d_moore(xdim::Integer, ydim::Integer, periodic::Bool=false)
  g = LightGraphs.grid([xdim, ydim], periodic=periodic)
  for x in 1:xdim
    for y in 1:ydim
      nodeid = coord2vertex((x, y), (xdim, ydim))
      connect_to = []
      if y == ydim
        if x == 1
          if periodic
            tp = (xdim, 1); push!(connect_to, tp)
            tp = (2, ydim-1); push!(connect_to, tp)
          else
            tp = (2, ydim-1); push!(connect_to, tp)
          end
        elseif x == xdim
          if periodic
            tp = (1, 1); push!(connect_to, tp)
            tp = (xdim-1, ydim-1); push!(connect_to, tp)
          else
            tp = (xdim-1, ydim-1); push!(connect_to, tp)
          end
        else
          if periodic
            tp = (x-1, 1); push!(connect_to, tp)
            tp = (x+1, 1); push!(connect_to, tp)
            tp = (x-1, y-1); push!(connect_to, tp)
            tp = (x+1, y-1); push!(connect_to, tp)
          else
            tp = (x-1, y-1); push!(connect_to, tp)
            tp = (x+1, y-1); push!(connect_to, tp)
          end
        end
      elseif y == 1
        if x == 1
          if periodic
            tp = (xdim, ydim); push!(connect_to, tp)
            tp = (2, y+1); push!(connect_to, tp)
          else
            tp = (2, y+1); push!(connect_to, tp)
          end
        elseif x == xdim
          if periodic
            tp = (1, y); push!(connect_to, tp)
            tp = (xdim-1, y+1); push!(connect_to, tp)
          else
            tp = (xdim-1, y+1); push!(connect_to, tp)
          end
        else
          if periodic
            tp = (x-1, y+1); push!(connect_to, tp)
            tp = (x+1, y+1); push!(connect_to, tp)
            tp = (x-1, ydim); push!(connect_to, tp)
            tp = (x+1, ydim); push!(connect_to, tp)
          else
            tp = (x-1, y+1); push!(connect_to, tp)
            tp = (x+1, y+1); push!(connect_to, tp)
          end
        end
      elseif y != 1 && y != ydim && x == 1
        if periodic
          tp = (x+1, y+1); push!(connect_to, tp)
          tp = (x+1, y-1); push!(connect_to, tp)
          tp = (xdim, y+1); push!(connect_to, tp)
          tp = (xdim, y-1); push!(connect_to, tp)
        else
          tp = (x+1, y+1); push!(connect_to, tp)
          tp = (x+1, y-1); push!(connect_to, tp)
        end
      elseif y != 1 && y != ydim && x == xdim
        if periodic
          tp = (x-1, y+1); push!(connect_to, tp)
          tp = (x-1, y-1); push!(connect_to, tp)
          tp = (1, y+1); push!(connect_to, tp)
          tp = (1, y-1); push!(connect_to, tp)
        else
          tp = (x-1, y+1); push!(connect_to, tp)
          tp = (x-1, y-1); push!(connect_to, tp)
        end
      else
          tp = (x+1, y+1); push!(connect_to, tp)
          tp = (x-1, y-1); push!(connect_to, tp)
          tp = (x+1, y-1); push!(connect_to, tp)
          tp = (x-1, y+1); push!(connect_to, tp)
      end

      for pp in connect_to
        add_edge!(g, nodeid, coord2vertex((pp[1], pp[2]), (xdim, ydim)))
      end
    end
  end
  return g
end

# 3d
function _grid(x::Integer, y::Integer, z::Integer, periodic=false, moore=false)
  g = _grid(x, y, periodic, moore)
  gp = deepcopy(g)
  gv = nv(gp)
  for layer in 2:z
    factor = layer-1
    for newnode in 1:gv
      newnodeid = newnode + (factor*gv)
      connect_to = newnodeid - gv
      if newnodeid > nv(g)
        add_vertex!(g)
      end
      add_edge!(g, newnodeid, connect_to)
      for nn in neighbors(gp, newnode)
        newneighbor = nn + (factor*gv)
        if newneighbor > nv(g)
          add_vertex!(g)
        end
        add_edge!(g, newnodeid, newneighbor)
      end
    end
  end
  return g
end

#######################################################################################
# vertex ⇄ coordinates
#######################################################################################
for f in (:coord2vertex, :vertex2coord)
  @eval ($f)(c, model::ABM) = ($f)(c, model.space)
  @eval ($f)(c, space::GridSpace) = ($f)(c, space.dimensions)
  @eval ($f)(c, space::GraphSpace) =
        error("This functionality does not make sense for a GraphSpace.")
end

"""
    coord2vertex(coord::NTuple{Int}, model_or_space) → n
    coord2vertex(coord::AbstractAgent, model_or_space) → n

Return the node number `n` of the given coordinates or the agent's position.
"""
function coord2vertex end

coord2vertex(agent::AbstractAgent, model::ABM) =
coord2vertex(agent.pos, model.space)

function coord2vertex(coord::Tuple{T, T, T}, dims) where T<: Integer
  if (dims[2] == 1 && dims[3] == 1) ||
     (dims[1] == 1 && dims[3] == 1) ||
     (dims[1] == 1 && dims[2] == 1) # 1D grid
    nodeid = maximum(coord[1])
  elseif dims[1] > 1 && dims[2] > 1 && dims[3] == 1  # 2D grid
    nodeid = coord2vertex((coord[1], coord[2]), (dims[1], dims[2]))
  else # 3D grid
    nodeid = (coord[2] * dims[1]) - (dims[1] - coord[1])  # (y * xlength) - (xlength - x)
    nodeid = nodeid + ((dims[1]*dims[2]) * (coord[3]-1))
  end
  return nodeid
end

function coord2vertex(coord::Tuple{Integer,Integer}, dims::Tuple{Integer,Integer})
  nodeid = (coord[2] * dims[1]) - (dims[1] - coord[1])  # (y * xlength) - (xlength - x)
  return nodeid
end

coord2vertex(coord::Tuple{Integer}, dims) = coord[1]
coord2vertex(coord::Integer, args...) = coord
coord2vertex(coord::Integer, m::ABM) = coord

"""
    vertex2coord(vertex::Integer, model_or_space) → coords

Returns the coordinates of a node given its number on the graph.
"""
function vertex2coord end

function vertex2coord(vertex::T, dims::Tuple{Integer, Integer, Integer}) where {T<:Integer}
  if dims[1] > 1 && dims[2] == 1 && dims[3] == 1  # 1D grid
    coord = (vertex, T(1), T(1))
  elseif dims[1] > 1 && dims[2] > 1 && dims[3] == 1  # 2D grid
    coord = vertex2coord(vertex, (dims[1], dims[2]))
    coord = (coord[1], coord[2], T(1))
  elseif dims[1] > 1 && dims[2] > 1 && dims[3] > 1  # 3D grid
    gridbasesize = dims[1]*dims[2]
    zcoord = ceil(T, vertex/gridbasesize)
    vertex2d = vertex - ((zcoord-T(1)) * gridbasesize)
    coord2d = vertex2coord(vertex2d, (dims[1], dims[2]))
    coord = (T(coord2d[1]), T(coord2d[2]), zcoord)
  else
    error("Wrong coords!")
  end
  return coord
end

function vertex2coord(vertex::T, dims::Tuple{Integer,Integer}) where {T<:Integer}
  x = T(vertex % dims[1])
  if x == 0
    x = T(dims[1])
  end
  y = ceil(T, vertex/dims[1])
  return (x, y)
end

vertex2coord(v::Tuple, model::ABM) = v

#######################################################################################
# finding specific nodes or agents
#######################################################################################
"""
    find_empty_nodes(model::ABM)

Returns the indices of empty nodes on the model space.
"""
function find_empty_nodes(model::ABM{A,<:DiscreteSpace}) where {A}
  ap = agent_positions(model)
  [i for i in 1:length(ap) if isempty(ap[i])]
end

"""
    pick_empty(model)

Return a random empty node or `0` if there are no empty nodes.
"""
function pick_empty(model::ABM{A,<:DiscreteSpace}) where {A}
  empty_nodes = find_empty_nodes(model)
  isempty(empty_nodes) && return 0
  rand(empty_nodes)
end

"""
    has_empty_nodes(model)

Return true if there are empty nodes in the `model`.
"""
function has_empty_nodes(model::ABM{A,<:DiscreteSpace}) where {A}
    any(isempty, agent_positions(model))
end

"""
    get_node_contents(node, model)

Return the ids of agents in the `node` of the model's space (which
is an integer for `GraphSpace` and a tuple for `GridSpace`).
"""
get_node_contents(n::Integer, model::ABM{A,<:DiscreteSpace}) where {A} = agent_positions(model)[n]

"""
    get_node_contents(agent::AbstractAgent, model)

Return all agents' ids in the same node as the `agent` (including the agent's own id).
"""
get_node_contents(agent::AbstractAgent, model::ABM{A,<:DiscreteSpace}) where {A} = get_node_contents(agent.pos, model)

function get_node_contents(coords::Tuple, model::ABM{A,<:DiscreteSpace}) where {A}
  node_number = coord2vertex(coords, model)
  agent_positions(model)[node_number]
end

"""
    get_node_agents(x, model)
Same as `get_node_contents(x, model)` but directly returns the list of agents
instead of just the list of IDs.
"""
get_node_agents(x, model::ABM{A,<:DiscreteSpace}) where {A} = [model[id] for id in get_node_contents(x, model)]

@deprecate id2agent(id::Integer, model::ABM) model[id]

"""
    space_neighbors(position, model::ABM, r) → ids

Return the ids of the agents neighboring the given `position` (which must match type
with the spatial structure of the `model`). `r` is the radius to search for agents.

For `DiscreteSpace`s `r` must be integer and defines higher degree neighbors.
For example, for `r=2` include first and second degree neighbors,
that is, neighbors and neighbors of neighbors.

For `ContinuousSpace`, `r` is real number and finds all neighbors within distance `r`
(based on the space's metric).

The [`node_neighbors`](@ref) keyword `neighbor_type` can also be used here to restrict
the spatial search on directed graphs.

    space_neighbors(agent::AbstractAgent, model::ABM [, r]) → ids

Call `space_neighbors(agent.pos, model, r)` but *exclude* the given
`agent` from the neighbors.
"""
function space_neighbors(agent::A, model::ABM{A,<:DiscreteSpace}, args...; kwargs...) where {A}
  all = space_neighbors(agent.pos, model, args...; kwargs...)
  d = findfirst(isequal(agent.id), all)
  d ≠ nothing && deleteat!(all, d)
  return all
end

function space_neighbors(pos, model::ABM{A, <: DiscreteSpace}, args...; kwargs...) where {A}
  node = coord2vertex(pos, model)
  nn = node_neighbors(node, model, args...; kwargs...)
  # We include the current node in the search since we are searching over space
  vcat(agent_positions(model)[node], agent_positions(model)[nn]...)
end

"""
    node_neighbors(node, model::ABM{A, <:DiscreteSpace}, r = 1) → nodes
Return all nodes that are neighbors to the given `node`, which can be an `Int` for
[`GraphSpace`](@ref), or a `NTuple{Int}` for [`GridSpace`](@ref).

    node_neighbors(agent, model::ABM{A, <:DiscreteSpace}, r = 1) → nodes
Same as above, but uses `agent.pos` as `node`.

Keyword argument `neighbor_type=:default` can be used to select differing neighbors
depending on the underlying graph type.
- `:default` returns neighbors of a vertex. If graph is directed, this is equivalent
to `:out`.
Using any of the following options on an undirected graph is the equivalent to `:default`.
- `:all` returns both `:in` and `:out` neighbors.
- `:in` returns incoming vertex neighbors.
- `:out` returns outgoing vertex neighbors.
"""
node_neighbors(
    agent::AbstractAgent,
    model::ABM{A,<:DiscreteSpace},
    args...;
    kwargs...,
) where {A} = node_neighbors(agent.pos, model, args...; kwargs...)
function node_neighbors(node_number::Integer, model::ABM{A, <: DiscreteSpace}; neighbor_type::Symbol=:default) where {A}
    @assert neighbor_type ∈ (:default, :all, :in, :out)
    neighborfn =
        if neighbor_type == :default
            neighbors
        elseif neighbor_type == :in
            inneighbors
        elseif neighbor_type == :out
            outneighbors
        else
            all_neighbors
        end
    neighborfn(model.space.graph, node_number)
end

function node_neighbors(node_coord::Tuple, model::ABM{A, <: DiscreteSpace}; kwargs...) where {A}
  node_number = coord2vertex(node_coord, model)
  nn = node_neighbors(node_number, model; kwargs...)
  nc = [vertex2coord(i, model) for i in nn]
  return nc
end

function node_neighbors(node_number::Integer, model::ABM{A, <: DiscreteSpace}, radius::Integer; kwargs...) where {A}
  neighbor_nodes = Set(node_neighbors(node_number, model; kwargs...))
  included_nodes = Set()
  for rad in 2:radius
    templist = Vector{Int}()
    for nn in neighbor_nodes
      if !in(nn, included_nodes)
        newns = node_neighbors(nn, model; kwargs...)
        for newn in newns
          push!(templist, newn)
        end
      end
    end
    for tt in templist
      push!(neighbor_nodes, tt)
    end
  end
  nlist = collect(neighbor_nodes)
  j = findfirst(a-> a==node_number, nlist)
  if j != nothing
    splice!(nlist, j)
  end
  return nlist
end

#######################################################################################
# Iteration over space
#######################################################################################
"""
    NodeIterator(model) → iterator

Create an iterator that returns node coordinates, if the space is a grid,
or otherwise node number, and the agent IDs in each node.
"""
struct NodeIterator{M<:ABM, S<:DiscreteSpace}
  model::M
  length::Int
end

NodeIterator(model::ABM{A,<:DiscreteSpace}) where {A} = NodeIterator(model, model.space)

function NodeIterator(m::M, s::S) where {M, S<:DiscreteSpace}
  L = LightGraphs.nv(s)
  return NodeIterator{M, S}(m, L)
end

Base.length(iter::NodeIterator) = iter.length

function Base.iterate(iter::NodeIterator{M,S}, state=1) where {M, S<:DiscreteSpace}
  state > iter.length && return nothing
  nodecontent = agent_positions(iter.model)[state]
  if S <: GridSpace
    node = vertex2coord(state, iter.model)
  else
    node = state
  end
  return ( (node, nodecontent), state+1 )
end

"""
    nodes(model; by = :id) -> ns
Return a vector of the node ids of the `model` that you can iterate over.
The `ns` are sorted depending on `by`:
* `:id` - just sorted by their number
* `:random` - randomly sorted
* `:population` - nodes are sorted depending on how many agents they accommodate.
  The more populated nodes are first.
"""
function nodes(model; by = :id)
    if by == :id
        return 1:nv(model)
    elseif by == :random
        return shuffle!(collect(1:nv(model)))
    elseif by == :population
        c = collect(1:nv(model))
        sort!(c, by = i -> length(get_node_contents(i, model)), rev = true)
        return c
    else
        error("unknown `by`.")
    end
end

ext: jl


text:
import ComponentArrays as CA
#import ComponentArrays: _get_index_axis

# function _get_index_axis(cv::ComponentVector, ax::AbstractAxis)
#     first(getaxes(cv)) == ax && return(cv) # no need to reassamble
#     # extract subvectors and reassamble
#     keys_ax = keys(ax)
#     # k = keys_ax[1]
#     tmp = map(keys_ax) do k
#         cvs = cv[KeepIndex(k)]
#         axs = ax[k].ax
#         @show cvs, axs, length(cvs), CA.last_index(ax)
#         !(axs isa CA.NullAxis) && CA.last_index(ax) != length(cvs) && error(
#             "Expect axis extracting component `$k` to extract n=$(length(cvs)) elements, " *
#             "but was $axs.")
#         cvk = _get_index_axis(cvs, axs)
#         ComponentVector(getdata(cvk), Viewax)
#     end
#     vcat(tmp...)
# end
# _get_index_axis(x, ax::CA.NullorFlatAxis) = x
# # in order to extract entire component, do not specify subaxes
# # e.g. (a=1) to match entire (a=(a1=1, a2=2))
# _get_index_axis(cv::ComponentVector, ax::CA.NullorFlatAxis) = cv # else method ambiguous

# function _set_index_axis!(cv::ComponentVector, s::ComponentVector)
#     ax = first(getaxes(s))
#     if first(getaxes(cv)) == ax
#         cv .= s
#         return(cv)
#     end
#     # setindex in each direct component
#     keys_ax = keys(ax)
#     for k in keys_ax
#         cvs = getproperty(cv, k)
#         ss = getproperty(s,k)
#         #@show cvs, ss
#         if cvs isa ComponentVector
#             _set_index_axis!(cvs, ss)
#         else
#             setproperty!(cv, k, ss)
#         end
#     end
#     cv
# end
# # function _set_index_axis!(cv::ComponentVector, sany)
# #     cv .= sany
# # end
# i_test = () -> begin
#     cvs = pset.is_state[i] ? u0l[k] : (pset.is_state[i] ? pl[k] : missing)

#     cv0 = ComponentVector(a=(a1=1,a2=2,a3=3),b=20)
#     cv = copy(cv0)
#     cvs = ComponentVector(a=(a1=1,a3=3))
#     s = cv0[cvs]
#     s.a.a3 = 6
#     _set_index_axis!(cv, s)

#     cvs = ComponentVector(a=1)
#     s = cv0[cvs]
#     s.a = [10,20,30]
#     _set_index_axis!(cv, s)
# end

# """
# assembles a new ComponentVector with some components replaced by corresponding
# components of s.
# The Base array used is Vector which is converted to the grand*parent type of cv,
# where `type_of(parent(x)) = type_of(x)`. Only base-types that support conversion
# from Vector without additional information are supported, e.g. StaticArray, but
# not AxisArray or NamedArray.
# Use `_set_index_axis!(cv, s)` to mutate an exsiting vector and keep its type
# """
# function _update_cv(cv::ComponentVector{T,A}, s::ComponentVector) where {T,A}
#     ax = first(getaxes(cv))
#     ax == first(getaxes(s)) && return s
#     keys_ax = keys(ax)
#     g = (ks ∈ keys_ax for ks in keys(first(getaxes(s))))
#     if !all(g)
#         ks_missing = keys(first(getaxes(s)))[.! g]
#         error(
#             "updating keys $(ks_missing) not found in updated compoenent vector with keys $ax")
#     end
#     tmp = map(keys_ax) do k
#         cvs = getproperty(cv, k)
#         !haskey(s, k) && return(cvs)
#         ss = getproperty(s,k)
#         #@show cvs, ss
#         if cvs isa ComponentVector
#             (ss isa ComponentVector) && return(_update_cv(cvs, ss))
#             (length(ss) != length(cvs)) && error(
#                 "Expected updating argument `s` to match ComponentVector $k=$cvs of " *
#                 "length($(length(cvs))), but was $ss.")
#             attach_axis(ss, first(getaxes(cvs)))
#         else
#             (length(ss) != length(cvs)) && error(
#                 "Expected updating key $k of length($(length(cvs))), with argument $ss.")
#             ss
#         end
#     end
#     res = ComponentVector(NamedTuple{keys_ax}(tmp))
#     # make sure to use the same base-type instead of vector
#     # direct parent maybe SubArray, which one cannot convert to, need to call parent(parent(...))
#     AT = typeof(fixpoint(parent,cv;fmap=typeof))
#     #AT = typeof(parent(cv))
#     attach_axis(convert(AT,getdata(res))::AT, first(getaxes(cv)))
# end

# function _ax_string_prefixed(any; prefix);  ""; end
# _ax_string_prefixed(sym::Symbol; prefix) = [string(sym)]
# function _ax_string_prefixed(v::AbstractArray; prefix); "[" .* string.(eachindex(v)) .* "]" end
# function _ax_string_prefixed(ax::CA.ShapedAxis; prefix="₊")
#     #@show "ShapedAxis", ax
#     if length(CA.indexmap(ax)) != 0
#         error("Unanticipated case: ax")
#     else
#         "[" .* _enum_dim(size(ax)) .* "]"
#     end
# end
# function _ax_string_prefixed(ax::AbstractAxis; prefix="₊")
#     ax_inner = Axis(ax)
#     if ax_inner == ax
#         _ax_string_prefixed(CA.indexmap(ax); prefix)
#     else
#         _ax_string_prefixed(ax_inner; prefix)
#     end
# end
# # use getaxes rather than axes to extract AbstractAxis instead of CombinedAxis
# # function _ax_string_prefixed(ax::CA.CombinedAxis; prefix="₊")
# #     #@show "combinedAxis"
# #     _ax_string_prefixed(CA._component_axis(ax); prefix)
# # end
# #_ax_symbol(t::Tuple{Vararg{<:AbstractAxis}}) = mapreduce(vcat, _ax_symbol, t)
# function _ax_string_prefixed(t::NamedTuple; prefix="₊")
#     #@show "NamedTuple", t, prefix
#     tmp = [prefix .* string(k) .* _ax_string_prefixed(v; prefix) for (k,v) in pairs(t)]
#     reduce(vcat, tmp; init=[])
# end
# #_extend = (s1,s2) -> isnothing(s2) ? s1 : s1 * "₊" * s2
# #_ax_symbols(ax) = Tuple(Symbol.(getindex.(_ax_string_prefixed(ax), Ref(2:end))))

# "Generate all cartesian indexing combinations"
# function _enum_dim(s,d)
#     d == 1 && return(string.(1:s[1]))
#     reduce(vcat, (_enum_dim(s,d-1) .* "," .* string(i) for i in 1:s[d]))
# end
# function _enum_dim(s)
#     _enum_dim(s, length(s))
# end

# CA.labels(ax) would be type piracy II
labels_noprefix(ax::AbstractAxis) = map(x -> x[2:end], _labels(ax))

_ax_symbols_tuple(ax::AbstractAxis; prefix="₊") = (labels_noprefix(ax) .|> (x -> replace(x,"." => prefix)) .|> Symbol) |> Tuple

_ax_symbols_vector(ax::AbstractAxis; prefix="₊") = (labels_noprefix(ax) .|> (x -> replace(x,"." => prefix)) .|> Symbol)::Vector{Symbol}

function _labels(x::AbstractAxis, nview::Int=0)
    #@info "Absract:$(typeof(x))"
    vcat((".$(key)" .* _labels(x[key]) for key in keys(x))...)
end
_labels(x::NamedTuple, nview::Int=0) = length(x) == 0 ? [""] : vcat((".$(key)" .* _labels(x[key]) for key in keys(x))...)

_labels(x::AbstractArray, nview::Int=0) = vcat(("[" * join(i.I, ",") * "]" for i in CartesianIndices(x))...)
function _labels(x, nview::Int=0)
    # @info "_labels(x)"
    # @show x, typeof(x)
    ""
end
_labels(x::CA.NullAxis, nview::Int=0) = ""

function _labels(x::CA.PartitionedAxis{PartSz,IdxMap},nview::Int) where {PartSz,IdxMap}
    la = _labels(IdxMap)
    ncomp = Int(nview / PartSz)
    #@show nview, PartSz, ncomp
    v = vcat(("[$i]"  for i in 1:ncomp)...)
    vcat((vi .* a for (a, vi) in Iterators.product(la,v))...)
end
function _labels(x::CA.ShapedAxis{Shape, IdxMap},nview::Int) where {Shape, IdxMap}
    la = _labels(IdxMap)
    v = vcat(("[" * join(i.I, ",") * "]" for i in CartesianIndices(Shape))...)
    vcat((vi .* a for (a, vi) in Iterators.product(la,v))...)
end

_labels(x::CA.ComponentIndex{N, FlatAxis}, nview::Int=0) where N = vcat(("[$i]" for i in eachindex(x.idx))...)
_labels(x::CA.ComponentIndex{N, <:AbstractAxis}, nview::Int=0) where N = _labels(x.ax, length(x.idx))

function _update_cv_top(cv::ComponentVector{TD}, s::ComponentVector{TS}) where {TD,TS}
    keyss = keys(s)
    mkeys = (!(k ∈ keys(cv)) for k in keyss)
    any(mkeys) && error(
        "The following keys to update were not found in destination " *
        string(keyss[collect(mkeys)]))
    # assigning a Dual to existing cv does not work, cannot use constructor
    #t = Tuple(getproperty(s,k) for k in keyss)
    #nt = NamedTuple{keyss}(t)
    #cvn = ComponentVector(cv; nt...)
    T_EL = promote_type(TD,TS)
    #k = first(keys(cv))
    tmp = map(keys(cv)) do k
        val_cv = val_cv0 = cv[KeepIndex(k)]
        # until KeepIndex behaviour is fixed in ComponentArrays
        res1 = !(k ∈ keyss) ? val_cv : begin
            TTS = typeof(similar(val_cv, T_EL))
            convert(TTS, s[KeepIndex(k)])::TTS
        end
    end
    T_C = typeof(similar(cv, T_EL))
    res = reduce(vcat, tmp)::T_C
end

# # TODO: wait for ComponentArrays implement length(Axis)
# """
#     subaxis(ax, sym::Symbol)
#     subaxis(ax, syms)

# Construct new reindexed axis for a top-level subcomponent of an axis.
# """
# function subaxis(ax::AbstractAxis, syms); _subaxis(ax, syms); end,
# function subaxis(ax::AbstractAxis, sym::Symbol); _subaxis(ax, (sym,)); end,
# function subaxis(cv::ComponentVector, syms); subaxis(first(getaxes(cv)), syms); end

# function _subaxis(ax::Axis,syms)
#     is_missing = map(s -> !(s ∈ keys(ax)), syms)
#     any(is_missing) && error(
#         "Expected subcomponents to be among keys(ax)=$(keys(ax)). Failed for " *
#         "$([s for (m,s) in zip(is_missing, syms) if m])")
#     length_axs = NamedTuple{keys(CA.indexmap(ax))}(map(length, CA.indexmap(ax)))
#     # start positions of subaxes in original and in target axis
#     start_axs = NamedTuple{keys(length_axs)}(
#         cumsum(vcat(1,collect(length_axs)[1:end-1])))
#     start_axt = NamedTuple{syms}(cumsum(
#         vcat(1,[p.second for p in pairs(length_axs) if p.first ∈ syms])[1:end-1]))
#     nts = map(syms) do sym
#         ax_sym = indexmap(ax)[sym]
#         reindex(ax_sym, start_axt[sym]-start_axs[sym])
#     end
#     Axis(NamedTuple{syms}(nts))
# end


text:
module SparseIR

import PyCall: pyimport, PyNULL, PyVector, PyObject

const sparse_ir = PyNULL()
const pyspr = PyNULL()
const pyaugment = PyNULL()
const pysampling = PyNULL()

function __init__()
    copy!(sparse_ir, pyimport_conda("sparse_ir", "sparse-ir", "spm-lab"))
    copy!(pyspr, pyimport("sparse_ir.spr"))
    copy!(pyaugment, pyimport("sparse_ir.augment"))
    copy!(pysampling, pyimport("sparse_ir.sampling"))
    return check_version_sparse_ir()
end

include("backend.jl")
include("types.jl")
include("kernel.jl")
include("poly.jl")
include("basis.jl")
include("augment.jl")
include("composite.jl")
include("sampling.jl")
include("spr.jl")
include("basis_set.jl")
include("exports.jl")

end


text:
export sachdr, writesac, writesacpz

# ============================================================================
# Utility functions not for export

# Bytes 305:308 as a littleendian Int32 should read 0x06 0x00 0x00 0x00; compare each end to 0x0a to allow older SAC versions (if version in same place?)
function should_bswap(io::IO)
  skip(io, 304)
  u = read(io, UInt8)
  skip(io, 2)
  v = read(io, UInt8)
  q::Bool = (
    # Least significant byte in u
    if 0x00 < u < 0x0a && v == 0x00
      false
    # Most significant byte in u
    elseif u == 0x00 && 0x00 < v < 0x0a
      true
    else
      error("Invalid SAC file.")
    end
    )
  return q
end

function write_sac_file(fname::String, x::AbstractArray, tdata::Array{Float32,1}, xy::Bool)
  open(fname, "w") do io
    write(io, BUF.sac_fv)
    write(io, BUF.sac_iv)
    write(io, BUF.sac_cv)
    if eltype(x) == Float32
      write(io, x)
    elseif eltype(x) <: Complex
      write(io, Float32.(real.(x)))
    else
      write(io, Float32.(x))
    end
    if xy
      write(io, tdata)
    end
  end
  return nothing
end

function fill_sac(si::Int64, nx::Int32, ts::Int64, id::Array{String,1})
  @assert nx ≤ typemax(Int32)
  tt = [Base.parse(Int32, k) for k in split(string(u2d(ts*μs)), r"[\.\:T\-]")]
  L = length(tt)
  (L < 7) && append!(tt, zeros(Int32, 7-L))

  # Ints
  y = tt[1]
  j = Int32(md2j(y, tt[2], tt[3]))
  BUF.sac_iv[1] = y
  BUF.sac_iv[2] = j
  for i in 3:6
    BUF.sac_iv[i] = tt[i+1]
  end
  BUF.sac_iv[10] = Int32(nx)

  # Floats
  BUF.sac_fv[6] = rem(ts, 1000)*1.0f-3
  BUF.sac_fv[7] = BUF.sac_fv[6] + BUF.sac_fv[1]*nx

  # Filename
  y_s = lpad(y, 4, '0')
  j_s = lpad(j, 3, '0')
  h_s = lpad(tt[4], 2, '0')
  m_s = lpad(tt[5], 2, '0')
  s_s = lpad(tt[6], 2, '0')
  ms_s = lpad(tt[7], 3, '0')
  fname = join([y_s, j_s, h_s, m_s, s_s, ms_s, id[1], id[2], id[3], id[4], "R.SAC"], '.')
  return fname
end

function read_sac_stream(f::IO, fv::Array{Float32,1}, iv::Array{Int32,1}, cv::Array{UInt8,1}, full::Bool, swap::Bool)
  read!(f, fv)
  read!(f, iv)
  read!(f, cv)
  if swap == true
    fv .= bswap.(fv)
    iv .= bswap.(iv)
  end
  nx = getindex(iv, 10)
  x = Array{Float32,1}(undef, nx)
  read!(f, x)
  if swap == true
    x .= bswap.(x)
  end

  # floats
  gain = Float64(getindex(fv, 4))
  if gain == Float64(sac_nul_f)
    gain = one(Float64)
  end
  loc = GeoLoc()
  j = 0
  lf = (:lat, :lon, :el, :az, :inc)
  for k in (32,33,34,58,59)
    j += 1
    val = getindex(fv, k)
    if val != sac_nul_f
      setfield!(loc, lf[j], Float64(val))
    end
  end

  # ints
  ts = mktime(getindex(iv, 1),
              getindex(iv, 2),
              getindex(iv, 3),
              getindex(iv, 4),
              getindex(iv, 5),
              getindex(iv, 6)*Int32(1000))
  b = getindex(fv, 6)
  if b != sac_nul_f
    ts += round(Int64, b*1.0f3)
  end

  # chars
  id = zeros(UInt8, 15)
  i = 1
  while i < 193
    c = getindex(cv, i)
    if c == sac_nul_start && i < 188
      val = getindex(cv, i+1:i+5)
      if val == sac_nul_Int8
        cv[i:i+5] .= 0x20
      end
    elseif c == 0x00
      setindex!(cv, i, 0x20)
    end
    # fill ID
    if i == 1
      i = fill_id!(id, cv, i, 8, 4, 8)
    elseif i == 17
      i = fill_id!(id, cv, i, 24, 10, 11)
    elseif i == 161
      i = fill_id!(id, cv, i, 168, 13, 15)
    elseif i == 169
      i = fill_id!(id, cv, i, 176, 1, 2)
    else
      i = i+1
    end
  end
  deleteat!(id, id.==0x00)

  # Create a seischannel
  C = SeisChannel()
  setfield!(C, :id, String(id))
  setfield!(C, :fs, Float64(1.0f0/getindex(fv,1)))
  setfield!(C, :gain, Float64(gain))
  setfield!(C, :loc, loc)
  t = Array{Int64,2}(undef, 2, 2)
  setindex!(t, one(Int64), 1)
  setindex!(t, Int64(nx), 2)
  setindex!(t, ts, 3)
  setindex!(t, zero(Int64), 4)
  setfield!(C, :t, t)
  setfield!(C, :x, x)

  # Create dictionary if full headers are desired
  if full == true
    D = getfield(C, :misc)

    fk = getindex(sac_keys, 1)
    ik = getindex(sac_keys, 2)
    ck = getindex(sac_keys, 3)

    # Parse floats
    z = zero(Int16)
    o = one(Int16)
    k = ""
    m = z
    while m < Int16(70)
      m += o
      if fv[m] != sac_nul_f
        k = getindex(fk, m)
        D[k] = getindex(fv, m)
      end
    end

    # Parse ints
    m = z
    while m < Int16(40)
      m += o
      if iv[m] != sac_nul_i
        k = getindex(ik, m)
        D[k] = getindex(iv, m)
      end
    end

    m = z
    j = o
    while j < Int16(24)
      n = Int16(j == 2 ? 16 : 8)
      p = m + n
      ii = m
      while ii < p
        ii += o
        if getindex(cv,ii) != 0x20
          k = getindex(ck, j)
          D[k] = String(getindex(cv, m+o:p))
          break
        end
      end
      m += n
      j += o
    end
  end

  return C
end

function read_sac_file!(S::SeisData, fname::String, fv::Array{Float32,1}, iv::Array{Int32,1}, cv::Array{UInt8,1}, full::Bool)
  f = open(fname, "r")
  q = should_bswap(f)
  seekstart(f)
  C = read_sac_stream(f, fv, iv, cv, full, q)
  setfield!(C, :src, fname)
  note!(C, string("+src: ", fname))
  close(f)
  push!(S,C)
  return nothing
end

# ============================================================================
# NOTE: Leave keyword arguments, even if some aren't type-stable!
# Use of "optional" variables instead is a 5x **slowdown**

"""
    sachdr(f)

Print formatted SAC headers from file `f` to stdout. Does not accept wildcard
file strings.
"""
function sachdr(fname::String)
  S = read_data("sac", fname, full=true)
  for i = 1:S.n
    D = getindex(getfield(S, :misc), i)
    src = getindex(getfield(S, :src), i)
    printstyled(string(src, "\n"), color=:light_green, bold=true)
    for k in sort(collect(keys(D)))
      println(stdout, uppercase(k), ": ", string(D[k]))
    end
  end
  return nothing
end

function reset_sacbuf()
  checkbuf_strict!(BUF.sac_fv, 70)
  checkbuf_strict!(BUF.sac_iv, 40)
  checkbuf_strict!(BUF.sac_cv, 192)

  fill!(BUF.sac_fv, sac_nul_f)
  fill!(BUF.sac_iv, sac_nul_i)
  for i in 1:24
    BUF.sac_cv[1+8*(i-1):8*i] = sac_nul_c
  end
  BUF.sac_cv[17:24] .= 0x20
  BUF.sac_iv[7] = Int32(6)
end

function fill_sac_id(id_str::String)
  id = split_id(id_str)

  # Chars, all segments
  ci = [169, 1, 25, 161]
  for j = 1:4
    sj = ci[j]
    if isempty(id[j])
      BUF.sac_cv[sj:sj+7] .= sac_nul_c
    else
      s = codeunits(id[j])
      L = min(length(s), 8)
      copyto!(BUF.sac_cv, sj, s, 1, L)
      if L < 8
        BUF.sac_cv[sj+L:sj+7] .= 0x20
      end
    end
  end
  return id
end

function write_sac_channel(S::GphysData, i::Int64, v::Int64, xy::Bool, fn::String)
  id = fill_sac_id(S.id[i])
  fs = S.fs[i]
  t = S.t[i]
  tdata = Array{Float32,1}(undef, 0)

  # Floats, all segments
  BUF.sac_fv[1] = (fs == 0.0 ? 0.0f0 : Float32(1.0/fs))
  BUF.sac_fv[4] = Float32(S.gain[i])
  for i in (32, 33, 34, 58, 59)
    BUF.sac_fv[i] = 0.0f0
  end
  if !isempty(S.loc[i])
    loc = S.loc[i]
    if typeof(loc) == GeoLoc
      BUF.sac_fv[32] = Float32(getfield(loc, :lat))
      BUF.sac_fv[33] = Float32(getfield(loc, :lon))
      BUF.sac_fv[34] = Float32(getfield(loc, :el))
      BUF.sac_fv[58] = Float32(getfield(loc, :az))
      BUF.sac_fv[59] = Float32(getfield(loc, :inc))
    end
  end

  if (xy == true || fs == 0.0)
    si = 1
    nx = Int32(length(S.x[i]))
    @assert nx ≤ typemax(Int32)
    ts = t[1,2]
    tdata = Float32.(μs*(t_expand(t, fs) .- ts))
    if fn == ""
      fname = fill_sac(si, nx, ts, id)
    else
      fill_sac(si, nx, ts, id)
      fname = fn
    end
    BUF.sac_fv[7] += sum(t[2:end,2])*μs
    BUF.sac_iv[16] = Int32(4)
    BUF.sac_iv[36] = zero(Int32)
    write_sac_file(fname, S.x[i], tdata, true)
    v > 0  && println(stdout, now(), ": Wrote SAC xy file ", fname, " from channel ", i)
  else
    W = t_win(S.t[i], fs)
    inds = x_inds(S.t[i])
    BUF.sac_iv[16] = one(Int32)
    BUF.sac_iv[36] = one(Int32)
    for j in 1:size(inds,1)
      si = inds[j,1]
      ei = inds[j,2]
      ts = W[j,1]
      nx = Int32(ei-si+1)
      if fn == ""
        fname = fill_sac(si, nx, ts, id)
      else
        fill_sac(si, nx, ts, id)
        fname = fn
      end
      vx = view(S.x[i], si:ei)
      write_sac_file(fname, vx, tdata, false)
      v > 0  && println(stdout, now(), ": Wrote SAC ts file ", fname, " from channel ", i, " segment ", j)
    end
  end

end

"""
    writesac(S::Union{SeisData}[; ts=false, v=0])

Write all data in SeisData structure `S` to auto-generated SAC files.

Keywords:
* `fname=FF` uses filename FF, rather than creating file names automatically.
(Only works with GphysChannel objects)
* `xy=true` writes generic x-y data with time as the independent variable.
"""
function writesac(S::GphysData; fn::String="", xy::Bool=false, v::Int64=KW.v)
  reset_sacbuf()
  for i = 1:S.n
    write_sac_channel(S, i, v, xy, fn)
  end
  return nothing
end

function writesac(S::GphysChannel;
  fname::String="",
  xy::Bool=false,
  v::Int64=KW.v)

  fstr = String(
    if fname == ""
      fname
    else
      if endswith(lowercase(fname), ".sac")
        fname
      else
        fname * ".sac"
      end
    end
    )
  writesac(SeisData(S), fn=fstr, xy=xy, v=v)
  return nothing
end

function add_pzchan!(S::GphysData, D::Dict{String, Any}, file::String)
  id  = D["NETWORK   (KNETWK)"] * "." *
        D["STATION    (KSTNM)"] * "." *
        D["LOCATION   (KHOLE)"] * "." *
        D["CHANNEL   (KCMPNM)"]
  i = findid(id, S)
  loc   = GeoLoc( lat = parse(Float64, D["LATITUDE"]),
                  lon = parse(Float64, D["LONGITUDE"]),
                  el  = parse(Float64, D["ELEVATION"]),
                  dep = parse(Float64, D["DEPTH"]),
                  az  = parse(Float64, D["AZIMUTH"]),
                  inc = parse(Float64, D["DIP"])-90.0
                )
  fs    = parse(Float64, D["SAMPLE RATE"])

  # gain, units; note, not "INSTGAIN", that's just a scalar multipler
  gu    = split(D["SENSITIVITY"], limit=2, keepempty=false)
  gain  = parse(Float64, gu[1])
  units = lowercase(String(gu[2]))
  if startswith(units, "(")
    units = units[2:end]
  end
  if endswith(units, ")")
    units = units[1:end-1]
  end
  units = fix_units(units2ucum(units))

  #= fix for poorly-documented fundamental shortcoming:
    "INPUT UNIT"         => "M"
    I have no idea why SACPZ uses displacement PZ =#
  u_in = fix_units(units2ucum(D["INPUT UNIT"]))
  Z = get(D, "Z", ComplexF32[])
  if u_in != units
    if u_in == "m" && units == "m/s"
      deleteat!(Z, 1)
    elseif u_in == "m" && units == "m/s2"
      deleteat!(Z, 1:2)
    end
  end

  resp = PZResp(parse(Float32, D["A0"]),
                0.0f0,
                get(D, "P", ComplexF32[]),
                Z
                )

  if i == 0
    # resp
    C = SeisChannel()
    setfield!(C, :id, id)
    setfield!(C, :name, D["DESCRIPTION"])
    setfield!(C, :loc, loc)
    setfield!(C, :fs, fs)
    setfield!(C, :gain, gain)
    setfield!(C, :resp, resp)
    setfield!(C, :units, units)
    setfield!(C, :src, file)
    setfield!(C, :misc, D)
    note!(C, "+src : read_sacpz " * file * ")")
    push!(S, C)
  else
    ts = Dates.DateTime(get(D, "START", "1970-01-01T00:00:00")).instant.periods.value*1000 - dtconst
    te = Dates.DateTime(get(D, "END", "2599-12-31T23:59:59")).instant.periods.value*1000 - dtconst
    t0 = isempty(S.t[i]) ? ts : S.t[i][1,2]
    if ts ≤ t0 ≤ te
      if S.fs[i] == 0.0
        S.fs[i] = fs
      end

      if isempty(S.units[i])
        S.units[i] = units
      end

      if S.gain[i] == 1.0
        S.gain[i] = gain
      end

      if typeof(S.resp[i]) == GenResp || isempty(S.resp[i])
        S.resp[i] = resp
      end

      if isempty(S.name[i])
        S.name[i] = D["DESCRIPTION"]
      end

      if isempty(S.loc[i])
        S.loc[i]  = loc
      end

      S.misc[i] = merge(D, S.misc[i])
    end
  end
  return nothing
end

@doc """
    read_sacpz!(S::GphysData, pzfile::String)

Read sacpz file `pzfile` into SeisIO struct `S`.

If an ID in the pz file matches channel `i` at times in `S.t[i]`:
* Fields :fs, :gain, :loc, :name, :resp, :units are overwritten if empty/unset
* Information from the pz file is merged into :misc if the corresponding keys
aren't in use.
""" read_sacpz!
function read_sacpz!(S::GphysData, file::String)
  io = open(file, "r")
  read_state = 0x00
  D = Dict{String, Any}()
  kv = Array{String, 1}(undef, 2)

  # Do this for each channel
  while true

    # EOF
    if eof(io)
      add_pzchan!(S, D, file)
      break
    end

    line = readline(io)

    # Header section
    if startswith(line, "*")
      if endswith(strip(line), "**")
        read_state += 0x01
        if read_state == 0x03
          add_pzchan!(S, D, file)
          read_state = 0x01
          D = Dict{String, Any}()
        end
      else
        kv .= strip.(split(line[2:end], ":", limit=2, keepempty=false))
        D[kv[1]] = kv[2]
      end

    # Zeros section
    elseif startswith(line, "ZEROS")
      N = parse(Int64, split(line, limit=2, keepempty=false)[2])
      D["Z"] = Array{Complex{Float32},1}(undef, N)
      for i = 1:N
        try
          mark(io)
          zc = split(readline(io), limit=2, keepempty=false)
          D["Z"][i] = complex(parse(Float32, zc[1]), parse(Float32, zc[2]))
        catch
          D["Z"][i:N] .= zero(ComplexF32)
          reset(io)
        end
      end

    # Poles section
    elseif startswith(line, "POLES")
      N = parse(Int64, split(line, limit=2, keepempty=false)[2])
      D["P"] = Array{Complex{Float32},1}(undef, N)
      for i = 1:N
        pc = split(readline(io), limit=2, keepempty=false)
        D["P"][i] = complex(parse(Float32, pc[1]), parse(Float32, pc[2]))
      end

    # Constant section
    elseif startswith(line, "CONSTANT")
      D["CONSTANT"] = String(split(line, limit=2, keepempty=false)[2])
    end
  end
  close(io)
  return S
end

@doc (@doc read_sacpz)
function read_sacpz(file::String)
  S = SeisData()
  read_sacpz!(S, file)
  return S
end

@doc """
    writesacpz(S::GphysData, pzfile::String)

Write fields from SeisIO struct `S` into sacpz file `pzfile`. Uses information
from fields :fs, :gain, :loc, :misc, :name, :resp, :units.
""" writesacpz
function writesacpz(S::GphysData, file::String)
  io = open(file, "w")
  for i in 1:S.n
    id = split(S.id[i], ".")
    created   = get(S.misc[i], "CREATED", string(u2d(time())))
    ts_str    = isempty(S.t[i]) ? "1970-01-01T00:00:00" : string(u2d(S.t[i][1,2]*1.0e-6))
    t_start   = get(S.misc[i], "START", ts_str)
    t_end     = get(S.misc[i], "END", "2599-12-31T23:59:59")
    unit_in   = get(S.misc[i], "INPUT UNIT", "?")
    unit_out  = get(S.misc[i], "OUTPUT UNIT", "?")

    Y = typeof(S.resp[i])
    if Y == GenResp
      a0 = 1.0
      P = S.resp[i][:,1]
      Z = deepcopy(S.resp[i][:,2])
    elseif Y in (PZResp, PZResp64)
      a0 = getfield(S.resp[i], :a0)
      P = getfield(S.resp[i], :p)
      Z = deepcopy(getfield(S.resp[i], :z))
    elseif Y == MultiStageResp
      j = 0
      for k = 1:length(S.resp[i].stage)
        stg = S.resp[i].stage[k]
        if typeof(stg) in (PZResp, PZResp64)
          j = k
          break
        end
      end
      if j == 0
        @warn(string("Skipped channel ", i, " (", id, "): incompatible response Type"))
        continue
      else
        a0 = getfield(S.resp[i].stage[j], :a0)
        P = getfield(S.resp[i].stage[j], :p)
        Z = deepcopy(getfield(S.resp[i].stage[j], :z))
      end
    end

    write(io, 0x2a)
    write(io, 0x20)
    write(io, fill!(zeros(UInt8, 34), 0x2a))
    write(io, 0x0a)

    write(io, "* NETWORK   (KNETWK): ", id[1], 0x0a)
    write(io, "* STATION    (KSTNM): ", id[2], 0x0a)
    write(io, "* LOCATION   (KHOLE): ", isempty(id[3]) ? "  " : id[3], 0x0a)
    write(io, "* CHANNEL   (KCMPNM): ", id[4], 0x0a)

    write(io, "* CREATED           : ", created, 0x0a)
    write(io, "* START             : ", t_start, 0x0a)
    write(io, "* END               : ", t_end, 0x0a)
    write(io, "* DESCRIPTION       : ", S.name[i], 0x0a)
    write(io, "* LATITUDE          : ", @sprintf("%0.6f", S.loc[i].lat), 0x0a)
    write(io, "* LONGITUDE         : ", @sprintf("%0.6f", S.loc[i].lon), 0x0a)
    write(io, "* ELEVATION         : ", string(S.loc[i].el), 0x0a)
    write(io, "* DEPTH             : ", string(S.loc[i].dep), 0x0a)
    write(io, "* DIP               : ", string(S.loc[i].inc+90.0), 0x0a)
    write(io, "* AZIMUTH           : ", string(S.loc[i].az), 0x0a)
    write(io, "* SAMPLE RATE       : ", string(S.fs[i]), 0x0a)

    for j in ("INPUT UNIT", "OUTPUT UNIT", "INSTTYPE", "INSTGAIN", "COMMENT")
      write(io, 0x2a, 0x20)
      write(io, rpad(j, 18))
      write(io, 0x3a, 0x20)
      v = get(S.misc[i], j, "")
      write(io, v)
      if j == "INSTGAIN" && v == ""
        write(io, "1.0E+00 (", S.units[i], ")")
      end
      write(io, 0x0a)
    end

    write(io, "* SENSITIVITY       : ", @sprintf("%12.6e", S.gain[i]), 0x20, 0x28, uppercase(S.units[i]), 0x29, 0x0a)
    NZ = length(Z)
    NP = length(P)
    write(io, "* A0                : ", @sprintf("%12.6e", a0), 0x0a)
    CONST = get(S.misc[i], "CONSTANT", string(a0*S.gain[i]))

    write(io, 0x2a)
    write(io, 0x20)
    write(io, fill!(zeros(UInt8, 34), 0x2a))
    write(io, 0x0a)

    # fix for units_in always being m
    if S.units[i] == "m/s"
      NZ += 1
      pushfirst!(Z, zero(ComplexF32))
    elseif S.units[i] == "m/s2"
      NZ += 2
      prepend!(Z, zeros(ComplexF32, 2))
    end
    write(io, "ZEROS\t", string(NZ), 0x0a)
    for i = 1:NZ
      write(io, 0x09, @sprintf("%+12.6e", real(Z[i])), 0x09, @sprintf("%+12.6e", imag(Z[i])), 0x09, 0x0a)
    end

    write(io, "POLES\t", string(NP), 0x0a)
    for i = 1:NP
      write(io, 0x09, @sprintf("%+12.6e", real(P[i])), 0x09, @sprintf("%+12.6e", imag(P[i])), 0x09, 0x0a)
    end

    write(io, "CONSTANT\t", CONST, 0x0a)
    write(io, 0x0a, 0x0a)
  end
  close(io)
  return nothing
end


text:
### A Pluto.jl notebook ###
# v0.14.2

using Markdown
using InteractiveUtils

# ╔═╡ 35e17eb8-a2f9-11eb-177b-d3861019871c
begin

	# what is my current DIR -
	_PATH_TO_ROOT = pwd()
	_PATH_TO_CONFIG = joinpath(_PATH_TO_ROOT, "config")
	_PATH_TO_DATA = joinpath(_PATH_TO_ROOT, "data")
	_PATH_TO_DALIY = joinpath(_PATH_TO_ROOT, "daily_adjusted")

	# activate me -
	import Pkg
	Pkg.activate(_PATH_TO_ROOT)
	Pkg.instantiate()

	# load external packages that I depend upon -
	using DataFrames
	using CSV
	using TOML
	using JSON
	using Dates
	using PooksoftAlphaVantageDataStore

	# should the download execute?
	_SHOULD_DOWNLOAD_EXECUTE = false
end

# ╔═╡ 216c8d63-1c67-4df3-9f4a-2a328866fced
html"""<style>
main {
    max-width: 1200px;
  	width: 70%;
	margin: auto;
}
"""

# ╔═╡ 958ca47b-4a52-4734-a93a-956c1c0a7035
begin

	# load the data containing the information about the SP500 companies -
	path_to_SP500_file = joinpath(_PATH_TO_DATA, "constituents.csv")
	df_SP500 = CSV.read(path_to_SP500_file, DataFrame)
end

# ╔═╡ ed933902-67e4-4a61-9f59-71bb9eccee28
begin

	# create a user object (we need this for the API call) -
	# note: you'll need to provide your own Configuration.json of the form:
	# {
	# 	"user_data":{
	# 		"alpha_vantage_api_email":"<YOUR EMAIL HERE>",
	# 		"alpha_vantage_api_key":"<YOUR API KEY HERE>"
	# 	}
	# }
	path_to_config_file = joinpath(_PATH_TO_CONFIG,"Configuration.json")

	# build the api user model -
	user_model_result = build_api_user_model(path_to_config_file)
	user_model = checkresult(user_model_result)

	# return nothing from this block -
	nothing
end

# ╔═╡ a1ae583c-2d7d-414b-9c74-498893a1b841
begin

	# let's download the daliy adjusted values for the ticker symbols in the constituents.csv file -
	# we wrap the download operation so it doesn't always execute -
	if (_SHOULD_DOWNLOAD_EXECUTE == true)

		# what is the current date?
		timestamp_value = "2021-04-21"

		# setup call -
		data_type = :csv
		outputsize = :full

		# get list of ticker symbols -
		list_of_ticker_symbols = df_SP500[:,1]
		for ticker_symbol in list_of_ticker_symbols

			# make a call to the API -
			df = execute_sts_adjusted_daily_api_call(user_model, ticker_symbol;
				data_type = data_type, outputsize = outputsize, logger=nothing) |> checkresult

			# dump to the result to disk -
			results_file_name = "DA-$(ticker_symbol)-T-$(timestamp_value).csv"
			path_to_results_file = joinpath(_PATH_TO_DALIY,results_file_name)
			CSV.write(path_to_results_file, df)

			# because of API limitations - we need to sleep a little bit here
			sleep(30)
		end
	end
end

# ╔═╡ 04f477a8-8e29-4726-a32f-aa31f19c11e4

# ╔═╡ f2f0de67-0388-4c91-ac87-5e8f1667928f

# ╔═╡ 9ad49f6f-61a3-4f6a-84f3-fc333a670fd1

# ╔═╡ e91ac071-2dfe-4b53-8262-4516da27f87c

# ╔═╡ 6535be62-000f-4b3a-abfd-b71ad8e344c2

# ╔═╡ Cell order:
# ╟─216c8d63-1c67-4df3-9f4a-2a328866fced
# ╠═35e17eb8-a2f9-11eb-177b-d3861019871c
# ╠═958ca47b-4a52-4734-a93a-956c1c0a7035
# ╠═ed933902-67e4-4a61-9f59-71bb9eccee28
# ╠═a1ae583c-2d7d-414b-9c74-498893a1b841
# ╟─04f477a8-8e29-4726-a32f-aa31f19c11e4
# ╟─f2f0de67-0388-4c91-ac87-5e8f1667928f
# ╟─9ad49f6f-61a3-4f6a-84f3-fc333a670fd1
# ╟─e91ac071-2dfe-4b53-8262-4516da27f87c
# ╟─6535be62-000f-4b3a-abfd-b71ad8e344c2


text:
@testset "PubMed Parse" begin
    #convert to an xml doc
    efetch_sample = read("efetch_sample.xml", String)
    efetch_doc = parse_string(efetch_sample)

    articles = root(efetch_doc)

    # Decide type of article based on structrure of efetch
    if name(articles) != "PubmedArticleSet"
        error("Not a PubMed search")
    end

    parsed = PubMed.parse_articles(articles)

    @test !ismissing(parsed["basic"][1,:pmid])

    for heading in parsed["mesh_heading"][:desc_uid]
        @test !ismissing(parsed["mesh_desc"][findall(parsed["mesh_desc"][:uid] .== heading),:name][1])
    end

    @test parsed["basic"][1,:title] == "Five Tips to Building a Successful Sleep Practice."
    @test parsed["basic"][1,:pmid] == 27483622
    @test parsed["author_ref"][1,:last_name] == "Poss"
    @test parsed["mesh_desc"][findall(parsed["mesh_heading"][1,:desc_uid] .== parsed["mesh_desc"][:uid]), :name][1] == "Bruxism"
    @test size(parsed["mesh_heading"][findall(parsed["mesh_heading"][:pmid] .== parsed["basic"][1,:pmid]), :])[1]== 6
end


text:
module ContextDataTypes

abstract type TextData end

include("context_iterator.jl")
include("cbowdata.jl")
include("skipgramdata.jl")
include("wrappers.jl")

function word2vecdata(vector_size::Integer, freq_table::Dict, model::Symbol)
    if model == :cbow
        return CBOWData(vector_size, freq_table)
    elseif model == :skipgram
        return SkipGramData(vector_size, freq_table)
    else
        error("model of type $model not supported")
    end
end

export Context, TextData, CBOWData, SkipGramData, word2vecdata,
        DownsampledData, downsample, MiniBatchData, minibatch

end