From 6b13f04da94f2f604241c8382e13b6f483a69da4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Fri, 21 Feb 2025 17:51:32 +0100
Subject: [PATCH 1/8] fix: tryopenfile updates :input on missing or failing
 input file

---
 src/files.jl | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/files.jl b/src/files.jl
index 100d81d..a9f9a12 100644
--- a/src/files.jl
+++ b/src/files.jl
@@ -104,9 +104,15 @@ function tryopenfile(controller, path; reload::Bool=false)
         turn_load_animation_on(controller)
     end
     #
+    if !isfile(path)
+        @error "Cannot find file" file=path
+        hub[:input][] = nothing
+        return
+    end
     records = loadfile(path)
     if isempty(records)
         @info "Cannot load file" file=path
+        hub[:input][] = nothing
         return
     elseif haskey(hub, :input)
         hub[:input][] = path
-- 
GitLab


From 09e0467fd3e63d890df6d2f2dff9de80be67c876 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Fri, 28 Mar 2025 17:44:06 +0100
Subject: [PATCH 2/8] feat: REST api (wip)

---
 Manifest.toml       |  23 +++--
 Project.toml        |   3 +
 README.md           |  13 ---
 src/LarvaTagger.jl  |   2 +
 src/REST/Client.jl  | 225 ++++++++++++++++++++++++++++++++++++++++++++
 src/REST/Model.jl   | 224 +++++++++++++++++++++++++++++++++++++++++++
 src/REST/REST.jl    |   8 ++
 src/REST/Server.jl  | 130 +++++++++++++++++++++++++
 src/Taggers.jl      |  83 ++++++++++++++--
 src/backends.jl     |  69 +++++++++-----
 src/cli_open.jl     |   2 +-
 src/editor.jl       |   5 +
 src/larvatagger.css |   3 +-
 src/wgl.jl          |  26 +++--
 test/rest_client.sh |  32 +++++++
 test/rest_server.sh | 177 ++++++++++++++++++++++++++++++++++
 16 files changed, 962 insertions(+), 63 deletions(-)
 create mode 100644 src/REST/Client.jl
 create mode 100644 src/REST/Model.jl
 create mode 100644 src/REST/REST.jl
 create mode 100644 src/REST/Server.jl
 create mode 100755 test/rest_client.sh
 create mode 100755 test/rest_server.sh

diff --git a/Manifest.toml b/Manifest.toml
index cf255d7..af56fc6 100644
--- a/Manifest.toml
+++ b/Manifest.toml
@@ -1,8 +1,8 @@
 # This file is machine-generated - editing it directly is not advised
 
-julia_version = "1.10.8"
+julia_version = "1.10.9"
 manifest_format = "2.0"
-project_hash = "029a32b6e21d0f2c52e6dccd0a009e9681bdff18"
+project_hash = "51a3effe31474c33a1cea491c41e13148b8635d0"
 
 [[deps.AbstractFFTs]]
 deps = ["LinearAlgebra"]
@@ -503,10 +503,10 @@ uuid = "0234f1f7-429e-5d53-9886-15a909be8d59"
 version = "1.14.2+1"
 
 [[deps.HTTP]]
-deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
-git-tree-sha1 = "1336e07ba2eb75614c99496501a8f4b233e9fafe"
+deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
+git-tree-sha1 = "c67b33b085f6e2faf8bf79a61962e7339a81129c"
 uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
-version = "1.10.10"
+version = "1.10.15"
 
 [[deps.HarfBuzz_jll]]
 deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"]
@@ -848,6 +848,11 @@ git-tree-sha1 = "1d2dd9b186742b0f317f2530ddcbf00eebb18e96"
 uuid = "23992714-dd62-5051-b70f-ba57cb901cac"
 version = "0.10.7"
 
+[[deps.MIMEs]]
+git-tree-sha1 = "1833212fd6f580c20d4291da9c1b4e8a655b128e"
+uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
+version = "1.0.0"
+
 [[deps.MKL_jll]]
 deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"]
 git-tree-sha1 = "f046ccd0c6db2832a9f639e2c669c6fe867e5f4f"
@@ -1025,7 +1030,7 @@ version = "3.2.4+0"
 [[deps.OpenLibm_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
-version = "0.8.1+2"
+version = "0.8.1+4"
 
 [[deps.OpenMPI_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML", "Zlib_jll"]
@@ -1062,6 +1067,12 @@ git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5"
 uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
 version = "1.6.3"
 
+[[deps.Oxygen]]
+deps = ["DataStructures", "Dates", "HTTP", "JSON3", "MIMEs", "Reexport", "RelocatableFolders", "Requires", "Sockets", "Statistics", "StructTypes"]
+git-tree-sha1 = "2ad010b0de6172faf1d09ed5e0837eb0b7355bd8"
+uuid = "df9a0d86-3283-4920-82dc-4555fc0d1d8b"
+version = "1.5.16"
+
 [[deps.PCRE2_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15"
diff --git a/Project.toml b/Project.toml
index 60848ef..4707dab 100644
--- a/Project.toml
+++ b/Project.toml
@@ -9,6 +9,8 @@ Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
 Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
 DocOpt = "968ba79b-81e4-546f-ab3a-2eecfa62a9db"
 Format = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
+HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
+JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
 LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
 Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
@@ -18,6 +20,7 @@ NyxWidgets = "c288fd06-43d3-4b04-8307-797133353e2e"
 Observables = "510215fc-4207-5dde-b226-833fc4488ee2"
 ObservationPolicies = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
 OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
+Oxygen = "df9a0d86-3283-4920-82dc-4555fc0d1d8b"
 PlanarLarvae = "c2615984-ef14-4d40-b148-916c85b43307"
 Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
 StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
diff --git a/README.md b/README.md
index 1b9a1b2..9996377 100644
--- a/README.md
+++ b/README.md
@@ -213,19 +213,6 @@ The `--browser` argument may open a new tab in your web browser, but this featur
 
 The first time the application is loaded, it may take a while for a window in your web browser to open, and the data to be plotted.
 
-### From the *Julia* interpreter
-
-As an alternative to the *larvatagger* script or command, in the *LarvaTagger* directory created above, launch the *Julia* interpreter:
-```
-julia --project=.
-```
-In the interpreter, to launch the editor, type:
-```
-julia> using LarvaTagger; display(larvaeditor("path/to/data/file"))
-```
-
-To exit the interpreter, type `exit()` or press Ctrl+D.
-
 ### macOS
 
 On some computers (typically macOS computers), the 2D larva view may show up twice as small as expected.
diff --git a/src/LarvaTagger.jl b/src/LarvaTagger.jl
index 00d344c..5c96f8e 100644
--- a/src/LarvaTagger.jl
+++ b/src/LarvaTagger.jl
@@ -32,6 +32,8 @@ include("players.jl")
 include("controllers.jl")
 include("Taggers.jl")
 using .Taggers
+include("REST/REST.jl")
+using .REST
 include("files.jl")
 include("backends.jl")
 include("cli_base.jl")
diff --git a/src/REST/Client.jl b/src/REST/Client.jl
new file mode 100644
index 0000000..61814f4
--- /dev/null
+++ b/src/REST/Client.jl
@@ -0,0 +1,225 @@
+module Client
+
+using PlanarLarvae.Datasets
+import ..Taggers: Taggers, Tagger
+import HTTP: HTTP
+using JSON3
+using OrderedCollections: OrderedDict
+using Observables
+
+export RemoteTagger, LTBackend, connect, listmodels, active_model_instance
+
+mutable struct RemoteTagger
+    endpoint::AbstractString
+    token::AbstractString # empty if not connected
+    backend::AbstractString
+    model_instance::AbstractString
+    output_filenames::Dict{String, String} # renaming rules
+end
+
+function RemoteTagger(endpoint, backend, model_instance)
+    RemoteTagger(endpoint, "", backend, model_instance, Dict{String, String}())
+end
+
+connected(tagger) = !isempty(tagger.token)
+
+function connect!(tagger)
+    tagger.token = gettoken(tagger)
+    return tagger
+end
+
+function gettoken(tagger)
+    if isempty(tagger.token)
+        resp = HTTP.get("$(tagger.endpoint)/get-token/$(tagger.backend)/$(tagger.model_instance)")
+        transcode(String, resp.body)
+    else
+        tagger.token
+    end
+end
+
+function url(tagger::RemoteTagger, switch)
+    token = tagger.token
+    @assert !isnothing(token)
+    return "$(tagger.endpoint)/$switch/$(tagger.backend)/$(tagger.model_instance)/$token"
+end
+
+function Base.close(tagger::RemoteTagger)
+    HTTP.get(url(tagger, "close"))
+    tagger.token = ""
+end
+
+function listfiles(tagger::RemoteTagger, srcdir::String)
+    resp = HTTP.get("$(url(tagger, "list-files"))/$srcdir")
+    @info "listfiles" transcode(String, resp.body)
+    String[]
+end
+
+function Taggers.pull(tagger::RemoteTagger, destdir::String)
+    cmd = url(tagger, "pull-file")
+    # destdir can be empty on macOS
+    destdir = isempty(destdir) ? pwd() : realpath(destdir) # strip end slash
+    destfiles = String[]
+    for filename in listfiles(tagger, "processed")
+        resp = HTTP.get("$cmd/$filename")
+        destfile = joinpath(destdir, filename)
+        open(destfile, "w") do f
+            write(f, resp.body)
+        end
+        push!(destfiles, destfile)
+    end
+    return destfiles
+end
+
+function Taggers.resetdata(tagger::RemoteTagger, dir=nothing)
+    query = url(tagger, "reset-data")
+    if !isnothing(dir)
+        query = "$query/$dir"
+    end
+    HTTP.get(query)
+end
+
+function Taggers.pushfile(tagger::RemoteTagger, src, dst)
+    @assert dst == basename(src)
+    Taggers.pushfile(tagger, src)
+end
+
+function Taggers.pushfile(tagger::RemoteTagger, src)
+    request = url(tagger, "push-file")
+    @info "dummy push-file" src
+end
+
+function Taggers.push(tagger::RemoteTagger, file::String, metadata; clean=true)
+    clean ? Taggers.resetdata(tagger) : Taggers.resetdata(tagger, "raw")
+    Taggers.push(tagger, file)
+    if !isnothing(metadata)
+        mktempdir() do dir
+            metadatafile = joinpath(dir, "metadata")
+            Datasets.to_json_file(metadatafile, metadata)
+            Taggers.pushfile(tagger, metadatafile)
+        end
+    end
+end
+
+Taggers.run(tagger::RemoteTagger, switch) = HTTP.get(url(tagger, switch); retry=false)
+
+Taggers.predict(tagger::RemoteTagger) = Taggers.run(tagger, "predict")
+
+Taggers.embed(tagger::RemoteTagger) = Taggers.run(tagger, "embed")
+
+
+struct LTBackend
+    endpoint
+    metadata
+    taggers
+    active_tagging_backend
+    active_tagger
+end
+
+function LTBackend(endpoint)
+    metadata = Dict{String, OrderedDict{Symbol, Union{String, Dict{String, OrderedDict{Symbol, String}}}}}()
+    taggers = OrderedDict{String, OrderedDict{String, RemoteTagger}}()
+    active_tagging_backend = Observable{Union{Nothing, String}}(nothing)
+    active_tagger = Observable{Union{Nothing, RemoteTagger}}(nothing)
+    on(active_tagging_backend) do tagging_backend
+        if isnothing(tagging_backend)
+            active_tagger[] = nothing
+        elseif isnothing(active_tagger[]) || active_tagger[].backend != tagging_backend
+            active_tagger[] = first(values(taggers[tagging_backend]))
+        else
+            notify(active_tagger)
+        end
+    end
+    on(active_tagger) do tagger
+        if !isnothing(tagger)
+            @info "Tagger selected" backend=tagger.backend instance=tagger.model_instance
+            if tagger.backend != active_tagging_backend[]
+                active_tagging_backend[] = tagger.backend
+            end
+        end
+    end
+    LTBackend(endpoint, metadata, taggers, active_tagging_backend, active_tagger)
+end
+
+function active_model_instance(back::LTBackend)
+    obs = Observable{Union{Nothing, String}}(nothing)
+    active_tagger = back.active_tagger
+    on(active_tagger) do tagger
+        obs[] = isnothing(tagger) ? nothing : tagger.model_instance
+    end
+    on(obs) do model_instance
+        tagger = active_tagger[]
+        if model_instance != tagger.model_instance
+            active_tagger[] = back.taggers[tagger.backend][model_instance]
+        end
+    end
+    return obs
+end
+
+function connect(back::LTBackend; refresh_rate=0.5, preselect_tagger=false)
+    while true
+        resp = HTTP.get("$(back.endpoint)/status")
+        resp = transcode(String, resp.body)
+        if resp == "up"
+            break
+        else
+            sleep(refresh_rate)
+        end
+    end
+    listtaggers(back)
+    #@info "listtaggers" back.taggers back.metadata
+    if preselect_tagger
+        back.active_tagging_backend[] = first(collect(keys(back.taggers)))
+    end
+end
+
+function simpleconvert(json::JSON3.Object)
+    OrderedDict(key => simpleconvert(val) for (key, val) in pairs(json))
+end
+
+simpleconvert(json::JSON3.Array) = simpleconvert.(json)
+
+simpleconvert(val) = val
+
+function listtaggers(back::LTBackend)
+    endpoint = back.endpoint
+    resp = HTTP.get("$(endpoint)/list-taggers")
+    resp = transcode(String, resp.body)
+    json = JSON3.read(resp)
+    @assert json isa JSON3.Array
+    taggers = simpleconvert(json)
+    for tagger in taggers
+        tagging_backend = tagger[:name]
+        back.metadata[tagging_backend] = OrderedDict(
+            key => (key === :models ? Dict(model[:name] => OrderedDict(
+                key′=> val′ for (key′, val′) in pairs(model) if key′!== :name)
+                for model in val) : val)
+            for (key, val) in pairs(tagger) if key !== :name)
+        back.taggers[tagging_backend] = OrderedDict(
+            model[:name] => RemoteTagger(endpoint, tagging_backend, model[:name])
+            for model in tagger[:models])
+    end
+    return taggers
+end
+
+listmodels(back::LTBackend) = listmodels(back, Val(false))
+
+listmodels(back::LTBackend, ::Val{false}) = collect(keys(back.taggers))
+
+function listmodels(back::LTBackend, ::Val{true})
+    map(back.active_tagging_backend) do tagging_backend
+        isnothing(tagging_backend) ? String[] : collect(keys(back.taggers[tagging_backend]))
+    end
+end
+
+function Taggers.predict(back::LTBackend, file::String; metadata=nothing)
+    tagger = back.active_tagger[]
+    isnothing(tagger) && throw("no active tagger")
+    connected(tagger) || connect!(tagger)
+    Taggers.push(tagger, file, metadata)
+    Taggers.predict(tagger)
+    labelfile = Taggers.pull(tagger, dirname(file))
+    @assert length(labelfile) == 1
+    return labelfile[1]
+end
+
+end
diff --git a/src/REST/Model.jl b/src/REST/Model.jl
new file mode 100644
index 0000000..781ce7a
--- /dev/null
+++ b/src/REST/Model.jl
@@ -0,0 +1,224 @@
+module Model
+
+import ..Taggers: Taggers, Tagger
+import HTTP: HTTP
+import JSON3
+using OrderedCollections: OrderedDict
+
+export LTBackend, gettoken, resetdata, listfiles, pushfile, pullfile, listtaggers, predict,
+       embed
+
+# pure part of the Server module (no global state)
+
+struct LTBackend
+    root
+    tokens
+    lock
+end
+
+function LTBackend()
+    root = Ref{AbstractString}("")
+    tokens = Dict{String, Dict{String, Dict{String, Float64}}}()
+    lock = ReentrantLock()
+    LTBackend(root, tokens, lock)
+end
+
+Base.lock(f::Function, backend::LTBackend) = lock(f, backend.lock)
+
+Base.isready(backend::LTBackend) = isdir(backend.root[])
+
+get!(dict::AbstractDict{K, V}, key::K) where {K, V} = Base.get!(dict, key, V())
+
+function get!(dict::AbstractDict{K, D}, key1::K, key2, keys...) where {K, D}
+    get!(get!(dict, key1), key2, keys...)
+end
+
+function gettagger(lt_backend, tagging_backend_dir, model_instance)
+    @assert isready(lt_backend)
+    tagging_backend_path = joinpath(lt_backend.root[], tagging_backend_dir)
+    @assert Taggers.isbackend(tagging_backend_path)
+    lock(lt_backend) do
+        tagger = Taggers.isolate(Tagger(tagging_backend_path, model_instance))
+        token = tagger.sandbox
+        tokens = get!(lt_backend.tokens, tagging_backend_dir, model_instance)
+        @assert token ∉ keys(tokens)
+        tokens[token] = time()
+        return tagger
+    end
+end
+
+function gettagger(lt_backend, tagging_backend_dir, model_instance, token)
+    @assert isready(lt_backend)
+    lock(lt_backend) do
+        tokens = lt_backend.tokens
+        @assert tagging_backend_dir in keys(tokens)
+        tokens = lt_backend.tokens[tagging_backend_dir]
+        @assert model_instance in keys(tokens)
+        tokens = tokens[model_instance]
+        @assert token in keys(tokens)
+    end
+    tagging_backend_path = joinpath(lt_backend.root[], tagging_backend_dir)
+    tagger = Tagger(tagging_backend_path, model_instance, token)
+    return tagger
+end
+
+##
+
+function gettoken(lt_backend, backend_dir, model_instance)
+    tagger = gettagger(lt_backend, backend_dir, model_instance)
+    return tagger.sandbox
+end
+
+function Base.close(lt_backend::LTBackend, backend_dir, model_instance, token)
+    tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+    Taggers.removedata(tagger)
+    lock(lt_backend) do
+        pop!(lt_backend.tokens[backend_dir][model_instance], token)
+    end
+    nothing
+end
+
+function resetdata(lt_backend, backend_dir, model_instance, token, datadir=nothing)
+    tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+    if isnothing(datadir)
+        Taggers.resetdata(tagger)
+    else
+        Taggers.resetdata(tagger, datadir)
+    end
+    nothing
+end
+
+function listfiles(lt_backend, backend_dir, model_instance, token, data_dir)
+    tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+    dir = Taggers.datadir(tagger, data_dir)
+    ls = []
+    for (parent, _, files) in walkdir(dir; follow_symlinks=true)
+        if parent == dir
+            append!(ls, files)
+        else
+            parent = relpath(parent, dir)
+            for file in files
+                push!(ls, joinpath(parent, file))
+            end
+        end
+    end
+    isempty(ls) ? "[]" : "[\"" * join(ls, "\", \"") * "\"]"
+end
+
+"""
+    pushfile(::LTBackend, ::HTTP.Request, backend_dir, model_instance, token)
+
+Handle `push-file` queries, *i.e.* receive file.
+"""
+function pushfile(lt_backend, request, backend_dir, model_instance, token)
+    tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+    @assert request.method == "POST"
+    body = request.body
+    @assert body isa Vector{UInt8}
+    k = findfirst(c -> c in (0x0d, 0x0a), body) - 1
+    filesep = body[1:k]
+    linesep = if body[k+2] == 0x0a
+        @assert body[k+1] == 0x0d
+        body[k+1:k+2]
+    else
+        body[k+1]
+    end
+    filesep = vcat(linesep, filesep)
+    n = length(filesep)
+    dk = length(linesep)
+    bodies = Vector{UInt8}[]
+    while k < length(body)
+        i, j = 1, k + dk + 1
+        for outer k in j:length(body)
+            if body[k] == filesep[i]
+                i += 1
+                if n < i
+                    push!(bodies, body[j:k-n])
+                    break
+                end
+            else
+                i = 1
+            end
+        end
+    end
+    linesep = transcode(String, linesep)
+    for body in bodies
+        k = HTTP.Parsers.find_end_of_header(body)
+        @assert 0 < k
+        rawheader = transcode(String, body[1:k-2dk])
+        content = body[k+1:end]
+        @debug "push-file" rawheader
+        header = Dict{Symbol, AbstractString}()
+        for line in split(rawheader, linesep)
+            if isempty(header)
+                for pair in split(line, "; ")
+                    parts = split(pair, '"')
+                    if length(parts) == 1
+                        key, val = split(pair, ": ")
+                        header[Symbol(key)] = val
+                    else
+                        key = parts[1]
+                        @assert endswith(key, '=')
+                        key = Symbol(key[1:end-1])
+                        val = join(parts[2:end-1], '"')
+                        header[key] = val
+                    end
+                end
+            else
+                @assert startswith(line, "Content-Type: ")
+                key, val = split(line, ": ")
+                header[Symbol(key)] = val
+            end
+        end
+        @info "push-file" header length(content)
+        filename = header[:filename]
+        dest = joinpath(Taggers.datadir(tagger, "raw"), filename)
+        open(dest, "w") do f
+            write(f, content)
+        end
+    end
+end
+
+function pullfile(lt_backend, backend_dir, model_instance, token, filename)
+    tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+    src = joinpath(Taggers.datadir(tagger, "processed"), filename)
+    header = Dict("Content-Disposition" => "form-data")
+    body = open(src)
+    return HTTP.Response(200, header, body)
+end
+
+function listtaggers(lt_backend)
+    inventory = Vector{OrderedDict{String, Any}}()
+    backends_dir = lt_backend.root[]
+    for tagging_backend in readdir(backends_dir)
+        tagging_backend_path = joinpath(backends_dir, tagging_backend)
+        Taggers.isbackend(tagging_backend_path) || continue
+        models_dir = joinpath(tagging_backend_path, "models")
+        models = [OrderedDict(
+            "name" => dir,
+            "description" => "",
+            "homepage" => "",
+        ) for dir in readdir(models_dir) if isdir(joinpath(models_dir, dir))]
+        push!(inventory, OrderedDict(
+            "name" => tagging_backend,
+            "description" => "",
+            "homepage" => "",
+            "models" => models,
+        ))
+    end
+    return JSON3.write(inventory)
+end
+
+function predict(lt_backend, backend_dir, model_instance, token)
+    tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+    # blocking; should we run async and expose a token-specific status api call?
+    Taggers.predict(tagger; skip_make_dataset=true)
+end
+
+function embed(lt_backend, backend_dir, model_instance, token)
+    tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+    # blocking, like predict
+    Taggers.embed(tagger; skip_make_dataset=true)
+end
+
+end
diff --git a/src/REST/REST.jl b/src/REST/REST.jl
new file mode 100644
index 0000000..e4465b9
--- /dev/null
+++ b/src/REST/REST.jl
@@ -0,0 +1,8 @@
+module REST
+
+using ..Taggers
+
+include("Server.jl")
+include("Client.jl")
+
+end
diff --git a/src/REST/Server.jl b/src/REST/Server.jl
new file mode 100644
index 0000000..4bd2bf5
--- /dev/null
+++ b/src/REST/Server.jl
@@ -0,0 +1,130 @@
+module Server
+
+using Oxygen; @oxidise
+import ..Taggers
+
+include("Model.jl")
+using .Model
+
+export run_backend
+
+function run_backend(backend::LTBackend; async=false, port=9285, kwargs...)
+    @assert isready(backend)
+    serve(; async=async, port=port, kwargs...)
+end
+
+# the Oxygen module has global state; as a consequence, the server must also have global
+# state
+const lt_backend = LTBackend()
+
+function run_backend(root::AbstractString; kwargs...)
+    lt_backend.root[] = root
+    run_backend(; kwargs...)
+end
+
+run_backend(; kwargs...) = run_backend(lt_backend; kwargs...)
+
+
+@get "/status" function(request)
+    return "up"
+end
+
+
+@get "/get-token/{backend_dir}/{model_instance}" function(
+        request,
+        backend_dir::String,
+        model_instance::String,
+    )
+    gettoken(lt_backend, backend_dir, model_instance)
+end
+
+
+@get "/close/{backend_dir}/{model_instance}/{token}" function(
+        request,
+        backend_dir::String,
+        model_instance::String,
+        token::String,
+    )
+    close(lt_backend, backend_dir, model_instance, token)
+end
+
+
+@get "/reset-data/{backend_dir}/{model_instance}/{token}" function(
+        request,
+        backend_dir::String,
+        model_instance::String,
+        token::String,
+    )
+    resetdata(lt_backend, backend_dir, model_instance, token)
+end
+
+
+@get "/reset-data/{backend_dir}/{model_instance}/{token}/{data_dir}" function(
+        request,
+        backend_dir::String,
+        model_instance::String,
+        token::String,
+        data_dir::String,
+    )
+    resetdata(lt_backend, backend_dir, model_instance, token, data_dir)
+end
+
+
+@get "/list-files/{backend_dir}/{model_instance}/{token}/{data_dir}" function(
+        request,
+        backend_dir::String,
+        model_instance::String,
+        token::String,
+        data_dir::String,
+    )
+    listfiles(lt_backend, backend_dir, model_instance, token, data_dir)
+end
+
+
+@post "/push-file/{backend_dir}/{model_instance}/{token}" function(
+        request,
+        backend_dir::String,
+        model_instance::String,
+        token::String,
+    )
+    pushfile(lt_backend, request, backend_dir, model_instance, token)
+end
+
+
+@get "/pull-file/{backend_dir}/{model_instance}/{token}/{filename}" function(
+        request,
+        backend_dir::String,
+        model_instance::String,
+        token::String,
+        filename::String,
+    )
+    pullfile(lt_backend, backend_dir, model_instance, token, filename)
+end
+
+
+@get "/list-taggers" function(request)
+    listtaggers(lt_backend)
+end
+
+
+@get "/predict/{backend_dir}/{model_instance}/{token}" function(
+        request,
+        backend_dir::String,
+        model_instance::String,
+        token::String,
+    )
+    predict(lt_backend, backend_dir, model_instance, token)
+end
+
+
+@get "/embed/{backend_dir}/{model_instance}/{token}" function(
+        request,
+        backend_dir::String,
+        model_instance::String,
+        token::String,
+    )
+    embed(lt_backend, backend_dir, model_instance, token)
+end
+
+
+end
diff --git a/src/Taggers.jl b/src/Taggers.jl
index b66ef40..e7864cc 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -11,14 +11,15 @@ struct Tagger
     output_filenames::Dict{String, String}
 end
 
-function Tagger(backend_dir::String, model_instance::String)
-    Tagger(backend_dir, model_instance, nothing, Dict{String, String}())
+function Tagger(backend_dir::String, model_instance::String,
+        sandbox::Union{Nothing, String}=nothing)
+    Tagger(backend_dir, model_instance, sandbox, Dict{String, String}())
 end
 Tagger(backend_dir, model_instance) = Tagger(string(backend_dir), string(model_instance))
 
 function isolate(tagger)
     rawdatadir = joinpath(tagger.backend_dir, "data", "raw")
-    mkdir(rawdatadir)
+    isdir(rawdatadir) || mkpath(rawdatadir)
     rawdatadir = mktempdir(rawdatadir; cleanup=false)
     Tagger(tagger.backend_dir, tagger.model_instance, basename(rawdatadir),
            tagger.output_filenames)
@@ -50,15 +51,15 @@ tagging_backend_command(tagger::Tagger) = tagging_backend_command(tagger.backend
 
 modeldir(tagger::Tagger) = joinpath(tagger.backend_dir, "models", tagger.model_instance)
 
-datadir(tagger::Tagger, stage::String) = joinpath(tagger.backend_dir, "data", stage,
+datadir(tagger::Tagger, stage) = joinpath(tagger.backend_dir, "data", stage,
         something(tagger.sandbox, tagger.model_instance))
 
-function reset(tagger::Tagger)
+function reset(tagger)
     resetmodel(tagger)
     resetdata(tagger)
 end
 
-function reset(dir::String)
+function reset(dir::AbstractString)
     try
         rm(dir; recursive=true)
     catch
@@ -69,13 +70,13 @@ end
 
 resetmodel(tagger::Tagger) = reset(modeldir(tagger))
 
-function resetdata(tagger::Tagger)
+function resetdata(tagger)
     for dir in ("raw", "interim", "processed")
         resetdata(tagger, dir)
     end
 end
 
-resetdata(tagger::Tagger, dir::String) = reset(datadir(tagger, dir))
+resetdata(tagger::Tagger, dir) = reset(datadir(tagger, dir))
 
 function removedata(tagger::Tagger)
     for dir in ("raw", "interim", "processed")
@@ -166,6 +167,72 @@ function push(tagger::Tagger, inputdata::String)
     return destination
 end
 
+## new implementation for push
+
+function pushfile(tagger, src, dst)
+    backend_name = basename(realpath(tagger.backend_dir))
+    @info "Pushing file to backend" backend=backend_name instance=tagger.model_instance src dst
+    src = normpath(src)
+    dst = normpath(joinpath(datadir(tagger, "raw"), dst))
+    if dst != src
+        dstdir = dirname(dst)
+        mkpath(dstdir)
+        open(src, "r") do f
+            open(dst, "w") do g
+                write(g, read(f))
+            end
+        end
+    end
+    return dst
+end
+
+pushfile(tagger, src) = pushfile(tagger, src, basename(src))
+
+function pushdir(tagger, src, dst=nothing)
+    raw = datadir(tagger, "raw")
+    dst = isnothing(dst) ? raw : joinpath(raw, dst)
+    symlink(src, dst)
+    return dst
+end
+
+function push(tagger, inputdata::AbstractString)
+    destination = nothing
+    if occursin('*', inputdata)
+        repository = Dataloaders.Repository(inputdata)
+        for file in Formats.find_associated_files(Dataloaders.files(repository))
+            srcfile = file.source
+            dstfile = relpath(srcfile, repository.root)
+            pushfile(tagger, srcfile, dstfile)
+        end
+    elseif isdir(inputdata)
+        srcdir = realpath(inputdata) # strip the end slashes
+        resetdata(tagger, "raw")
+        pushdir(tagger, srcdir)
+    elseif endswith(inputdata, ".txt")
+        files_by_dir = Dict{String, Vector{String}}()
+        for file in readlines(inputdata)
+            parent = dirname(file)
+            push!(get!(files_by_dir, parent, String[]), abspath(file))
+        end
+        for (dir, files) in pairs(files_by_dir)
+            for file in Formats.find_associated_files(files)
+                srcfile = file.source
+                dstfile = joinpath(dir, basename(srcfile))
+                pushfile(tagger, srcfile, dstfile)
+            end
+        end
+    else
+        for file in Formats.find_associated_files(abspath(inputdata))
+            srcfile = file.source
+            dstfile = pushfile(tagger, srcfile)
+            if isnothing(destination)
+                destination = dstfile
+            end
+        end
+    end
+    return destination
+end
+
 function pull(tagger::Tagger, dest_dir::String)
     proc_data_dir = datadir(tagger, "processed")
     isdir(proc_data_dir) || throw("no processed data directory found")
diff --git a/src/backends.jl b/src/backends.jl
index 41f5c9b..d734ad1 100644
--- a/src/backends.jl
+++ b/src/backends.jl
@@ -46,26 +46,40 @@ Backends(controller, location) = Backends(controller, string(location))
 
 function getbackends(controller, location=nothing)
     controller = gethub(controller)
-    try
-        return controller[:backends]
-    catch
-        backends = Backends(controller, location)
-        Observables.notify(backends.active_backend)
-        controller[:backends] = backends
-        return backends
+    if haskey(controller, :backends)
+        controller[:backends]
+    else
+        if !isnothing(location) && startswith(location, "http://")
+            back = REST.Client.LTBackend(location)
+            REST.Client.connect(back; preselect_tagger=true)
+            controller[:backends] = back
+        else
+            backends = Backends(controller, location)
+            Observables.notify(backends.active_backend)
+            controller[:backends] = backends
+        end
     end
 end
 
-function Taggers.push(model::Backends, file::String; clean=true, metadata=true)
+get_active_backend(backends::Backends) = backends.active_backend
+get_model_instances(backends::Backends) = backends.model_instances
+get_model_instance(backends::Backends) = backends.model_instance
+get_backend_names(backends::Backends) = backends.backends
+
+get_active_backend(back::REST.Client.LTBackend) = back.active_tagging_backend
+get_model_instances(back::REST.Client.LTBackend) = REST.Client.listmodels(back, Val(true))
+get_model_instance(back::REST.Client.LTBackend) = REST.Client.active_model_instance(back)
+get_backend_names(back::REST.Client.LTBackend) = REST.Client.listmodels(back)
+
+function Taggers.push(model::Backends, file::String; clean=true, metadata=nothing)
     tagger = gettagger(model)
     clean ? resetdata(tagger) : resetdata(tagger, "raw")
     dest_file = Taggers.push(tagger, file)
-    if metadata
+    if !isnothing(metadata)
         # save the metadata to file, so that the backend can reproduce them in the output
         # file for predicted labels
         dest_file = joinpath(dirname(dest_file), "metadata")
-        metadata = Observables.to_value(getmetadatatable(model.controller))
-        PlanarLarvae.Datasets.to_json_file(dest_file, asdict(metadata))
+        PlanarLarvae.Datasets.to_json_file(dest_file, metadata)
     end
 end
 
@@ -76,25 +90,32 @@ function Taggers.pull(model::Backends, destdir::String)
     return Taggers.pull(tagger, destdir)
 end
 
-function Taggers.predict(model::Backends)
+function Taggers.predict(model::Backends, file::String; metadata=nothing)
     isnothing(model.model_instance[]) && throw("no model selected")
     backend_dir = joinpath(model.location, model.active_backend[])
     model_instance = model.model_instance[]
     isnothing(model_instance) && throw("no model instance selected")
-    turn_load_animation_on(model.controller)
+    tagger = Tagger(backend_dir, model_instance)
+    #
+    Taggers.push(model, file; metadata=metadata)
+    # TODO: make the skip_make_dataset option discoverable in the backend
+    predict(tagger; skip_make_dataset=true)
+    labelfile = Taggers.pull(model, dirname(file))
+    @assert length(labelfile) == 1
+    return labelfile[1]
+end
+
+function Taggers.predict(controller::ControllerHub, back)
+    inputfile = controller[:input][]
+    isnothing(inputfile) && throw("no loaded files")
+    @assert ispath(inputfile)
+    metadata = asdict(Observables.to_value(getmetadatatable(controller)))
+    turn_load_animation_on(controller)
     try
-        # TODO: make the skip_make_dataset option discoverable in the backend
-        predict(Tagger(backend_dir, model_instance); skip_make_dataset=true)
+        resultingfile = predict(back, inputfile; metadata=metadata)
+        tryopenfile(controller, resultingfile; reload=true)
     catch
-        turn_load_animation_off(model.controller)
+        turn_load_animation_off(controller)
         rethrow()
     end
 end
-
-function Taggers.predict(model::Backends, file::String)
-    Taggers.push(model, file)
-    predict(model)
-    labelfile = Taggers.pull(model, dirname(file))
-    @assert length(labelfile) == 1
-    tryopenfile(model.controller, labelfile[1]; reload=true)
-end
diff --git a/src/cli_open.jl b/src/cli_open.jl
index a1d46b6..73210d8 100644
--- a/src/cli_open.jl
+++ b/src/cli_open.jl
@@ -54,7 +54,7 @@ function main(args=ARGS; exit_on_error=false)
     infile = parsed_args["<file-path>"]
     if isempty(infile)
         infile = nothing
-    elseif !isfile(infile)
+    elseif !(startswith(infile, "http://") || isfile(infile))
         if isdir(infile)
             dataroot = infile
             infile = nothing
diff --git a/src/editor.jl b/src/editor.jl
index ad40fb6..4891530 100644
--- a/src/editor.jl
+++ b/src/editor.jl
@@ -43,6 +43,11 @@ function larvaeditor(path=nothing;
         enable_delete=false,
         kwargs...)
 
+    if path isa AbstractString && startswith(path, "http://")
+        backend_directory = path
+        path = nothing
+    end
+
     # to (re-)load a file, the app is reloaded with the filepath as sole information
     # from previous session
     T = Ref{Union{Nothing, String}}
diff --git a/src/larvatagger.css b/src/larvatagger.css
index 013b8a4..943cd77 100644
--- a/src/larvatagger.css
+++ b/src/larvatagger.css
@@ -146,7 +146,8 @@ table {
 }
 
 button:disabled {
-	--text-opacity: 0.5;
+	/* --text-opacity: 0.5; */
+	opacity: 0.5;
 	pointer-events: none;
 }
 
diff --git a/src/wgl.jl b/src/wgl.jl
index fb4640d..6674dc5 100644
--- a/src/wgl.jl
+++ b/src/wgl.jl
@@ -979,33 +979,39 @@ function addtosavequeue!(c::LarvaFilter, id)
 end
 
 struct BackendMenu
-    backends::Backends
+    backends
+    models
     send2backend::NyxWidgets.Button
     dom_id
 end
 
 function backendmenu(controller, location)
+    controller = gethub(controller)
     backends = getbackends(controller, location)
-    disabled = map(isempty, backends.model_instances)
+    models = get_model_instances(backends)
+    disabled = if isnothing(controller[:input][])
+        true
+    else
+        map(isempty, models)
+    end
     send2backend = NyxWidgets.Button("Autotag"; disabled=disabled)
     on(send2backend) do _
-        predict(backends, controller[:input][])
+        predict(controller, backends)
     end
-    BackendMenu(backends, send2backend, dom_id())
+    BackendMenu(backends, models, send2backend, dom_id())
 end
 
 function lowerdom(bs::BackendMenu)
-    models = bs.backends.model_instances
-    model_instance = bs.backends.model_instance
+    model_instance = get_model_instance(bs.backends)
     update_model_instance = js"(evt)=>{ $model_instance.notify(evt.srcElement.value); }"
-    select_dom = DOM.select(DOM.option(model; value=model) for model in models[];
+    select_dom = DOM.select(DOM.option(model; value=model) for model in bs.models[];
                             onchange=update_model_instance,
                             class=css_button)
-    active_backend = bs.backends.active_backend
+    active_backend = get_active_backend(bs.backends)
     update_active_backend = js"(evt)=>{ $active_backend.notify(evt.srcElement.value); }"
     return DOM.div(DOM.label("Backend"),
                    DOM.select(DOM.option(backend; value=backend)
-                              for backend in bs.backends.backends;
+                              for backend in get_backend_names(bs.backends);
                               onchange=update_active_backend,
                               class=css_button),
                    DOM.label("Model instance"),
@@ -1015,7 +1021,7 @@ function lowerdom(bs::BackendMenu)
 end
 function Bonito.jsrender(session::Session, bs::BackendMenu)
     node = lowerdom(session, bs)
-    onjs(session, bs.backends.model_instances, js"""(options) => {
+    onjs(session, bs.models, js"""(options) => {
          select = document.querySelectorAll($(dom_selector(bs)) + ' > select')[1];
          LarvaTagger.updateSelectOptions(select, options);
     }""")
diff --git a/test/rest_client.sh b/test/rest_client.sh
new file mode 100755
index 0000000..23daccb
--- /dev/null
+++ b/test/rest_client.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+set -m
+
+this_script=$(realpath "${BASH_SOURCE[0]}")
+larvatagger_jl_project_root=$(dirname "$(dirname "${this_script}")")
+larvatagger_project_root=$(dirname "${larvatagger_jl_project_root}")
+
+tagging_backend=MaggotUBA
+tagging_model=20230311
+lt_backend_port=9285
+
+# pre-requisites are similar to rest_server.sh
+
+# pre-compile
+julia +1.10 --project="${larvatagger_jl_project_root}" -q -e "using LarvaTagger"
+
+# run and background the backend server
+JULIA_PROJECT="${larvatagger_project_root}/TaggingBackends" \
+  julia +1.10 --project="${larvatagger_jl_project_root}" -i \
+  -e "using LarvaTagger.REST.Server; run_backend(\"${larvatagger_project_root}\"; port=${lt_backend_port})" &
+lt_backend_pid=$!
+
+# run the frontend server
+JULIA="julia +1.10" ${larvatagger_jl_project_root}/scripts/larvatagger-gui.jl http://localhost:${lt_backend_port}
+
+# scenarii:
+# * click on "Autotag" prior to loading any files (if not disabled, as with a bug in v0.19)
+# * load a tracking data file, and then click on "Autotag"
+
+kill $lt_backend_pid
+wait $lt_backend_pid
diff --git a/test/rest_server.sh b/test/rest_server.sh
new file mode 100755
index 0000000..6de171f
--- /dev/null
+++ b/test/rest_server.sh
@@ -0,0 +1,177 @@
+#!/bin/bash
+
+set -m
+
+this_script=$(realpath "${BASH_SOURCE[0]}")
+larvatagger_jl_project_root=$(dirname "$(dirname "${this_script}")")
+larvatagger_project_root=$(dirname "${larvatagger_jl_project_root}")
+
+tagging_backend=MaggotUBA
+tagging_model=20230311
+lt_backend_port=9285
+
+# assumed directory structure:
+# the MaggotUBA-adapter, TaggingBackends, NyxArtefacts and LarvaTagger.jl projects are siblings in the ${larvatagger_project_root} directory;
+# the MaggotUBA-adapter project is available both as directory MaggotUBA-adapter and link MaggotUBA (${tagging_backend});
+# the MaggotUBA-adapter tagging backend contains two trained models (or model instances): 20230311 and 20230311-0;
+# the NyxArtefacts project is available as directory Artefacts;
+# MaggotUBA is installed with JULIA_PROJECT pointing at the TaggingBackends directory
+
+[ -d "${larvatagger_project_root}/${tagging_backend}/models/${tagging_model}" ] || exit "tagging backend or model not found"
+
+if [ -z "`julia +1.10 -v 2>/dev/null`" ]; then
+  juliaup add 1.10
+fi
+
+# run and background the backend server
+JULIA_PROJECT="${larvatagger_project_root}/TaggingBackends" \
+  julia +1.10 --project="${larvatagger_jl_project_root}" -iq \
+  -e "using LarvaTagger.REST.Server; run_backend(\"${larvatagger_project_root}\"; port=${lt_backend_port})" &
+lt_backend_pid=$!
+
+# wait for the server to be ready
+while [ "`curl -s http://localhost:${lt_backend_port}/status`" != "up" ]; do
+  sleep 1
+done
+echo "status: success"
+
+# get a session token for a tagging backend
+token=`curl -sS http://localhost:${lt_backend_port}/get-token/${tagging_backend}/${tagging_model}`
+
+if [ "${token:0:3}" = "jl_" ]; then
+  echo "get-token: success"
+else
+  echo "get-token: failure"
+  echo "expected: string starting with \"jl_\""
+  echo "got: $token"
+fi
+
+# send one text file, one binary file
+curl -sS -F "f=@${larvatagger_jl_project_root}/README.md" -F "g=@${larvatagger_project_root}/Artefacts/PlanarLarvae/trx_small.tgz" http://localhost:${lt_backend_port}/push-file/${tagging_backend}/${tagging_model}/${token} 1>/dev/null
+
+generated_files=(`ls "${larvatagger_project_root}/${tagging_backend}/data/raw/${token}"`)
+
+expected_files=("README.md" "trx_small.tgz")
+
+if [ ${#generated_files[@]} -eq ${#expected_files[@]} -a \
+  "${generated_files[0]}" = "${expected_files[0]}" -a \
+  "${generated_files[1]}" = "${expected_files[1]}" ]; then
+  echo "push-file: success"
+else
+  echo "push-file: failure"
+fi
+
+# discover the sent files
+discovered_files=`curl -sS http://localhost:${lt_backend_port}/list-files/${tagging_backend}/${tagging_model}/${token}/raw`
+
+expected_files='["README.md", "trx_small.tgz"]'
+
+if [ "$discovered_files" = "$expected_files" ]; then
+  echo "list-files: success"
+else
+  echo "list-files: failure"
+  echo "expected: $expected_files"
+  echo "got: $discovered_files"
+fi
+
+# make file in processed data dir
+test_filename=label.json
+
+mkdir -p "${larvatagger_project_root}/${tagging_backend}/data/processed/${token}"
+cat <<EOF >"${larvatagger_project_root}/${tagging_backend}/data/processed/${token}/${test_filename}"
+{
+}
+EOF
+
+# retrieve file from processed data dir
+retrieved_content=`curl -sS http://localhost:${lt_backend_port}/pull-file/${tagging_backend}/${tagging_model}/${token}/${test_filename}`
+
+expected_content="{
+}"
+
+if [ "$retrieved_content" = "$expected_content" ]; then
+  echo "pull-file: success"
+else
+  echo "pull-file: failure"
+  echo "expected file content:"
+  echo "$expected_content"
+  echo "got:"
+  echo "$retrieved_content"
+fi
+
+# request file deletion
+curl -sS http://localhost:${lt_backend_port}/reset-data/${tagging_backend}/${tagging_model}/${token}/raw 1>/dev/null
+
+stored_files=`ls "${larvatagger_project_root}/${tagging_backend}/data/raw/${token}/"`
+if [ -z "$stored_files" ]; then
+  echo "reset-data: success"
+else
+  echo "reset-data: failure"
+  echo "expected empty list"
+  echo "got:"
+  echo "$stored_files"
+fi
+
+# discover the backend(s) and their models
+discovered_backends=`curl -sS http://localhost:${lt_backend_port}/list-backends`
+
+backend_metadata() {
+  local desc1=
+  local link1=
+  local desc2=
+  local link2=
+  local desc3=
+  local link3=
+  echo "{\"name\":\"$1\",\"description\":\"$desc1\",\"homepage\":\"$link1\",\"models\":[{\"name\":\"20230311\",\"description\":\"$desc2\",\"homepage\":\"$link2\"},{\"name\":\"20230311-0\",\"description\":\"$desc3\",\"homepage\":\"$link3\"}]}"
+}
+expected_backends="[`backend_metadata $tagging_backend`,`backend_metadata ${tagging_backend}-adapter`]"
+
+if [ "$discovered_backends" = "$expected_backends" ]; then
+  echo "list-backends: success"
+else
+  echo "list-backends: failure"
+  echo "expected:"
+  echo "$expected_backends"
+  echo "got:"
+  echo "$discovered_backends"
+  # further diagnose
+  model_instances=(`ls "${larvatagger_project_root}/${tagging_backend}/models"`)
+  if ! [ ${#model_instances[@]} -eq 2 -a "${model_instances[0]}" = "20230311" -a  "${model_instances[1]}" = "20230311-0" ]; then
+    echo "tagging backend is not adequately set up"
+  fi
+fi
+
+cp "${larvatagger_jl_project_root}"/20140805_101522@FCF_attP2_1500062@UAS_TNT_2_0003@t5@p_8_45s1x30s0s#p_8_105s10x2s10s#n#n@100.* "${larvatagger_project_root}/${tagging_backend}/data/raw/${token}/"
+
+# if python does not find TaggingBackends, refresh LT install:
+#(cd ../MaggotUBA-adapter && (cd ../TaggingBackends && PYTHON="`poetry env info`/bin/python" julia +1.10 --project=. -e 'using Pkg; Pkg.instantiate()'))
+#(cd ../MaggotUBA-adapter && JULIA_PROJECT="`realpath ../TaggingBackends`" poetry install)
+
+# run the predict function of the tagging backend
+curl -sS http://localhost:${lt_backend_port}/predict/${tagging_backend}/${tagging_model}/${token} 1>/dev/null
+
+generated_files=(`ls "${larvatagger_project_root}/${tagging_backend}/data/processed/${token}/"`)
+
+if [ ${#generated_files[@]} = 1 -a "${generated_files[0]}" = "predicted.label" ]; then
+  echo "predict: success"
+else
+  echo "predict: failure"
+  echo "expected: predicted.label"
+  echo "got:"
+  echo "$generated_files"
+fi
+
+# clear session data
+curl -sS http://localhost:${lt_backend_port}/close/${tagging_backend}/${tagging_model}/${token} 1>/dev/null
+
+if ! [ -d "${larvatagger_project_root}/${tagging_backend}/data/raw/${token}" ] && \
+  ! [ -d "${larvatagger_project_root}/${tagging_backend}/data/interim/${token}" ] && \
+  ! [ -d "${larvatagger_project_root}/${tagging_backend}/data/processed/${token}" ]; then
+  echo "close: success"
+else
+  echo "close: failure"
+fi
+
+echo "tests complete; terminating the backend (a stack trace will follow)"
+kill $lt_backend_pid
+wait $lt_backend_pid
-- 
GitLab


From 53a879ee638b99ba90f0caeb15b585635a3be8e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Mon, 31 Mar 2025 11:48:11 +0200
Subject: [PATCH 3/8] feat: REST api

---
 Manifest-v1.11.toml | 523 +++++++++++++++++++++++---------------------
 Manifest.toml       | 474 ++++++++++++++++++++-------------------
 src/REST/Client.jl  |  12 +-
 test/rest_client.sh |  17 +-
 4 files changed, 543 insertions(+), 483 deletions(-)

diff --git a/Manifest-v1.11.toml b/Manifest-v1.11.toml
index 53ceb33..4a3fd72 100644
--- a/Manifest-v1.11.toml
+++ b/Manifest-v1.11.toml
@@ -1,8 +1,8 @@
 # This file is machine-generated - editing it directly is not advised
 
-julia_version = "1.11.3"
+julia_version = "1.11.4"
 manifest_format = "2.0"
-project_hash = "423cfe5e2b370a6df6f85f87fd1406fd07136176"
+project_hash = "ba336c605739d728e2989784d94690e67b22a25d"
 
 [[deps.AbstractFFTs]]
 deps = ["LinearAlgebra"]
@@ -22,12 +22,13 @@ version = "0.4.5"
 
 [[deps.Adapt]]
 deps = ["LinearAlgebra", "Requires"]
-git-tree-sha1 = "50c3c56a52972d78e8be9fd135bfb91c9574c140"
+git-tree-sha1 = "f7817e2e585aa6d924fd714df1e2a84be7896c60"
 uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
-version = "4.1.1"
-weakdeps = ["StaticArrays"]
+version = "4.3.0"
+weakdeps = ["SparseArrays", "StaticArrays"]
 
     [deps.Adapt.extensions]
+    AdaptSparseArraysExt = "SparseArrays"
     AdaptStaticArraysExt = "StaticArrays"
 
 [[deps.AdaptivePredicates]]
@@ -43,9 +44,9 @@ version = "1.1.3"
 
 [[deps.Animations]]
 deps = ["Colors"]
-git-tree-sha1 = "e81c509d2c8e49592413bfb0bb3b08150056c79d"
+git-tree-sha1 = "e092fa223bf66a3c41f9c022bd074d916dc303e7"
 uuid = "27a7e980-b3e6-11e9-2bcd-0b925532e340"
-version = "0.4.1"
+version = "0.4.2"
 
 [[deps.ArgTools]]
 uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
@@ -99,10 +100,10 @@ uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
 version = "1.2.2"
 
 [[deps.Bzip2_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "8873e196c2eb87962a2048b3b8e08946535864a1"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e"
 uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
-version = "1.0.8+2"
+version = "1.0.9+0"
 
 [[deps.CEnum]]
 git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
@@ -121,15 +122,15 @@ version = "1.0.1+0"
 
 [[deps.Cairo_jll]]
 deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
-git-tree-sha1 = "009060c9a6168704143100f36ab08f06c2af4642"
+git-tree-sha1 = "2ac646d71d0d24b44f3f8c84da8c9f4d70fb67df"
 uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
-version = "1.18.2+1"
+version = "1.18.4+0"
 
 [[deps.ChainRulesCore]]
 deps = ["Compat", "LinearAlgebra"]
-git-tree-sha1 = "3e4b134270b372f2ed4d4d0e936aabaefc1802bc"
+git-tree-sha1 = "1713c74e00545bfe14605d2a2be1712de8fbcb58"
 uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
-version = "1.25.0"
+version = "1.25.1"
 weakdeps = ["SparseArrays"]
 
     [deps.ChainRulesCore.extensions]
@@ -143,33 +144,37 @@ version = "1.4.0"
 
 [[deps.CodecZlib]]
 deps = ["TranscodingStreams", "Zlib_jll"]
-git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759"
+git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9"
 uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
-version = "0.7.6"
+version = "0.7.8"
 
 [[deps.ColorBrewer]]
-deps = ["Colors", "JSON", "Test"]
-git-tree-sha1 = "61c5334f33d91e570e1d0c3eb5465835242582c4"
+deps = ["Colors", "JSON"]
+git-tree-sha1 = "e771a63cc8b539eca78c85b0cabd9233d6c8f06f"
 uuid = "a2cac450-b92f-5266-8821-25eda20663c8"
-version = "0.4.0"
+version = "0.4.1"
 
 [[deps.ColorSchemes]]
 deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"]
-git-tree-sha1 = "13951eb68769ad1cd460cdb2e64e5e95f1bf123d"
+git-tree-sha1 = "403f2d8e209681fcbd9468a8514efff3ea08452e"
 uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
-version = "3.27.0"
+version = "3.29.0"
 
 [[deps.ColorTypes]]
 deps = ["FixedPointNumbers", "Random"]
-git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d"
+git-tree-sha1 = "c7acce7a7e1078a20a285211dd73cd3941a871d6"
 uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
-version = "0.11.5"
+version = "0.12.0"
+weakdeps = ["StyledStrings"]
+
+    [deps.ColorTypes.extensions]
+    StyledStringsExt = "StyledStrings"
 
 [[deps.ColorVectorSpace]]
 deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"]
-git-tree-sha1 = "a1f44953f2382ebb937d60dafbe2deea4bd23249"
+git-tree-sha1 = "8b3b6f87ce8f65a2b4f857528fd8d70086cd72b1"
 uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
-version = "0.10.0"
+version = "0.11.0"
 weakdeps = ["SpecialFunctions"]
 
     [deps.ColorVectorSpace.extensions]
@@ -177,9 +182,9 @@ weakdeps = ["SpecialFunctions"]
 
 [[deps.Colors]]
 deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
-git-tree-sha1 = "362a287c3aa50601b0bc359053d5c2468f0e7ce0"
+git-tree-sha1 = "64e15186f0aa277e174aa81798f7eb8598e0157e"
 uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
-version = "0.12.11"
+version = "0.13.0"
 
 [[deps.Compat]]
 deps = ["TOML", "UUIDs"]
@@ -198,9 +203,9 @@ version = "1.1.1+0"
 
 [[deps.ConcurrentUtilities]]
 deps = ["Serialization", "Sockets"]
-git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1"
+git-tree-sha1 = "d9d26935a0bcffc87d2613ce14c527c99fc543fd"
 uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
-version = "2.4.2"
+version = "2.5.0"
 
 [[deps.ConstructionBase]]
 git-tree-sha1 = "76219f1ed5771adbb096743bff43fb5fdd4c1157"
@@ -225,9 +230,9 @@ version = "1.16.0"
 
 [[deps.DataStructures]]
 deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
-git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82"
+git-tree-sha1 = "4e1fe97fdaed23e9dc21d4d664bea76b65fc50a0"
 uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
-version = "0.18.20"
+version = "0.18.22"
 
 [[deps.DataValueInterfaces]]
 git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
@@ -240,10 +245,10 @@ uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
 version = "1.11.0"
 
 [[deps.DelaunayTriangulation]]
-deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "PrecompileTools", "Random"]
-git-tree-sha1 = "89df54fbe66e5872d91d8c2cd3a375f660c3fd64"
+deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "Random"]
+git-tree-sha1 = "5620ff4ee0084a6ab7097a27ba0c19290200b037"
 uuid = "927a84f5-c5f4-47a5-9785-b46e178433df"
-version = "1.6.1"
+version = "1.6.4"
 
 [[deps.DelimitedFiles]]
 deps = ["Mmap"]
@@ -275,9 +280,9 @@ version = "1.11.0"
 
 [[deps.Distributions]]
 deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
-git-tree-sha1 = "3101c32aab536e7a27b1763c0797dba151b899ad"
+git-tree-sha1 = "0b4190661e8a4e51a842070e7dd4fae440ddb7f4"
 uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
-version = "0.25.113"
+version = "0.25.118"
 
     [deps.Distributions.extensions]
     DistributionsChainRulesCoreExt = "ChainRulesCore"
@@ -296,10 +301,9 @@ uuid = "968ba79b-81e4-546f-ab3a-2eecfa62a9db"
 version = "0.5.0"
 
 [[deps.DocStringExtensions]]
-deps = ["LibGit2"]
-git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d"
+git-tree-sha1 = "e7b7e6f178525d17c720ab9c081e4ef04429f860"
 uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
-version = "0.9.3"
+version = "0.9.4"
 
 [[deps.Downloads]]
 deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
@@ -313,9 +317,9 @@ uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
 version = "2.2.4+0"
 
 [[deps.EnumX]]
-git-tree-sha1 = "bdb1942cd4c45e3c678fd11569d5cccd80976237"
+git-tree-sha1 = "bddad79635af6aec424f53ed8aad5d7555dc6f00"
 uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
-version = "1.0.4"
+version = "1.0.5"
 
 [[deps.ExactPredicates]]
 deps = ["IntervalArithmetic", "Random", "StaticArrays"]
@@ -325,20 +329,20 @@ version = "2.2.8"
 
 [[deps.ExceptionUnwrapping]]
 deps = ["Test"]
-git-tree-sha1 = "dcb08a0d93ec0b1cdc4af184b26b591e9695423a"
+git-tree-sha1 = "d36f682e590a83d63d1c7dbd287573764682d12a"
 uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4"
-version = "0.1.10"
+version = "0.1.11"
 
 [[deps.Expat_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7"
+git-tree-sha1 = "d55dffd9ae73ff72f1c0482454dcf2ec6c6c4a63"
 uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
-version = "2.6.2+0"
+version = "2.6.5+0"
 
 [[deps.Extents]]
-git-tree-sha1 = "81023caa0021a41712685887db1fc03db26f41f5"
+git-tree-sha1 = "063512a13dbe9c40d999c439268539aa552d1ae6"
 uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
-version = "0.1.4"
+version = "0.1.5"
 
 [[deps.FFMPEG_jll]]
 deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
@@ -348,21 +352,25 @@ version = "6.1.2+0"
 
 [[deps.FFTW]]
 deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
-git-tree-sha1 = "4820348781ae578893311153d69049a93d05f39d"
+git-tree-sha1 = "7de7c78d681078f027389e067864a8d53bd7c3c9"
 uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
-version = "1.8.0"
+version = "1.8.1"
 
 [[deps.FFTW_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
 git-tree-sha1 = "4d81ed14783ec49ce9f2e168208a12ce1815aa25"
 uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
-version = "3.3.10+1"
+version = "3.3.10+3"
 
 [[deps.FileIO]]
 deps = ["Pkg", "Requires", "UUIDs"]
-git-tree-sha1 = "62ca0547a14c57e98154423419d8a342dca75ca9"
+git-tree-sha1 = "b66970a70db13f45b7e57fbda1736e1cf72174ea"
 uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
-version = "1.16.4"
+version = "1.17.0"
+weakdeps = ["HTTP"]
+
+    [deps.FileIO.extensions]
+    HTTPExt = "HTTP"
 
 [[deps.FilePaths]]
 deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"]
@@ -372,9 +380,9 @@ version = "0.8.3"
 
 [[deps.FilePathsBase]]
 deps = ["Compat", "Dates"]
-git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361"
+git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2"
 uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
-version = "0.9.22"
+version = "0.9.24"
 weakdeps = ["Mmap", "Test"]
 
     [deps.FilePathsBase.extensions]
@@ -405,9 +413,9 @@ version = "0.8.5"
 
 [[deps.Fontconfig_jll]]
 deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"]
-git-tree-sha1 = "db16beca600632c95fc8aca29890d83788dd8b23"
+git-tree-sha1 = "21fac3c77d7b5a9fc03b0ec503aa1a6392c34d2b"
 uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
-version = "2.13.96+0"
+version = "2.15.0+0"
 
 [[deps.Format]]
 git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc"
@@ -422,38 +430,38 @@ version = "4.1.1"
 
 [[deps.FreeType2_jll]]
 deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
-git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc"
+git-tree-sha1 = "2c5512e11c791d1baed2049c5652441b28fc6a31"
 uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
-version = "2.13.2+0"
+version = "2.13.4+0"
 
 [[deps.FreeTypeAbstraction]]
 deps = ["ColorVectorSpace", "Colors", "FreeType", "GeometryBasics"]
-git-tree-sha1 = "84dfe824bd6fdf2a5d73bb187ff31b5549b2a79c"
+git-tree-sha1 = "d52e255138ac21be31fa633200b65e4e71d26802"
 uuid = "663a7486-cb36-511b-a19d-713bb74d65c9"
-version = "0.10.4"
+version = "0.10.6"
 
 [[deps.FriBidi_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "1ed150b39aebcc805c26b93a8d0122c940f64ce2"
+git-tree-sha1 = "846f7026a9decf3679419122b49f8a1fdb48d2d5"
 uuid = "559328eb-81f9-559d-9380-de523a88c83c"
-version = "1.0.14+0"
+version = "1.0.16+0"
 
 [[deps.GeoFormatTypes]]
-git-tree-sha1 = "59107c179a586f0fe667024c5eb7033e81333271"
+git-tree-sha1 = "8e233d5167e63d708d41f87597433f59a0f213fe"
 uuid = "68eda718-8dee-11e9-39e7-89f7f65f511f"
-version = "0.4.2"
+version = "0.4.4"
 
 [[deps.GeoInterface]]
-deps = ["Extents", "GeoFormatTypes"]
-git-tree-sha1 = "826b4fd69438d9ce4d2b19de6bc2f970f45f0f88"
+deps = ["DataAPI", "Extents", "GeoFormatTypes"]
+git-tree-sha1 = "294e99f19869d0b0cb71aef92f19d03649d028d5"
 uuid = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
-version = "1.3.8"
+version = "1.4.1"
 
 [[deps.GeometryBasics]]
-deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
-git-tree-sha1 = "b62f2b2d76cee0d61a2ef2b3118cd2a3215d3134"
+deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "PrecompileTools", "Random", "StaticArrays"]
+git-tree-sha1 = "65e3f5c519c3ec6a4c59f4c3ba21b6ff3add95b0"
 uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
-version = "0.4.11"
+version = "0.5.7"
 
 [[deps.Gettext_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
@@ -463,27 +471,27 @@ version = "0.21.0+0"
 
 [[deps.Giflib_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "0224cce99284d997f6880a42ef715a37c99338d1"
+git-tree-sha1 = "6570366d757b50fabae9f4315ad74d2e40c0560a"
 uuid = "59f7168a-df46-5410-90c8-f2779963d0ec"
-version = "5.2.2+0"
+version = "5.2.3+0"
 
 [[deps.Glib_jll]]
 deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"]
-git-tree-sha1 = "674ff0db93fffcd11a3573986e550d66cd4fd71f"
+git-tree-sha1 = "b0036b392358c80d2d2124746c2bf3d48d457938"
 uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
-version = "2.80.5+0"
+version = "2.82.4+0"
 
 [[deps.Graphite2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011"
+git-tree-sha1 = "01979f9b37367603e2848ea225918a3b3861b606"
 uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
-version = "1.3.14+0"
+version = "1.3.14+1"
 
 [[deps.GridLayoutBase]]
 deps = ["GeometryBasics", "InteractiveUtils", "Observables"]
-git-tree-sha1 = "fc713f007cff99ff9e50accba6373624ddd33588"
+git-tree-sha1 = "dc6bed05c15523624909b3953686c5f5ffa10adc"
 uuid = "3955a311-db13-416c-9275-1d80ed98e5e9"
-version = "0.11.0"
+version = "0.11.1"
 
 [[deps.Grisu]]
 git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
@@ -503,34 +511,34 @@ version = "0.17.2"
     MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195"
 
 [[deps.HDF5_jll]]
-deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"]
-git-tree-sha1 = "38c8874692d48d5440d5752d6c74b0c6b0b60739"
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"]
+git-tree-sha1 = "e94f84da9af7ce9c6be049e9067e511e17ff89ec"
 uuid = "0234f1f7-429e-5d53-9886-15a909be8d59"
-version = "1.14.2+1"
+version = "1.14.6+0"
 
 [[deps.HTTP]]
-deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
-git-tree-sha1 = "bc3f416a965ae61968c20d0ad867556367f2817d"
+deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
+git-tree-sha1 = "c67b33b085f6e2faf8bf79a61962e7339a81129c"
 uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
-version = "1.10.9"
+version = "1.10.15"
 
 [[deps.HarfBuzz_jll]]
 deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"]
-git-tree-sha1 = "401e4f3f30f43af2c8478fc008da50096ea5240f"
+git-tree-sha1 = "55c53be97790242c29031e5cd45e8ac296dadda3"
 uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
-version = "8.3.1+0"
+version = "8.5.0+0"
 
 [[deps.Hwloc_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "dd3b49277ec2bb2c6b94eb1604d4d0616016f7a6"
+git-tree-sha1 = "f93a9ce66cd89c9ba7a4695a47fd93b4c6bc59fa"
 uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8"
-version = "2.11.2+0"
+version = "2.12.0+0"
 
 [[deps.HypergeometricFunctions]]
 deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
-git-tree-sha1 = "7c4195be1649ae622304031ed46a2f4df989f1eb"
+git-tree-sha1 = "68c173f4f449de5b438ee67ed0c9c748dc31a2ec"
 uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
-version = "0.3.24"
+version = "0.3.28"
 
 [[deps.Hyperscript]]
 deps = ["Test"]
@@ -540,9 +548,9 @@ version = "0.0.5"
 
 [[deps.ImageAxes]]
 deps = ["AxisArrays", "ImageBase", "ImageCore", "Reexport", "SimpleTraits"]
-git-tree-sha1 = "2e4520d67b0cef90865b3ef727594d2a58e0e1f8"
+git-tree-sha1 = "e12629406c6c4442539436581041d372d69c55ba"
 uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac"
-version = "0.6.11"
+version = "0.6.12"
 
 [[deps.ImageBase]]
 deps = ["ImageCore", "Reexport"]
@@ -552,9 +560,9 @@ version = "0.1.7"
 
 [[deps.ImageCore]]
 deps = ["ColorVectorSpace", "Colors", "FixedPointNumbers", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "PrecompileTools", "Reexport"]
-git-tree-sha1 = "b2a7eaa169c13f5bcae8131a83bc30eff8f71be0"
+git-tree-sha1 = "8c193230235bbcee22c8066b0374f63b5683c2d3"
 uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
-version = "0.10.2"
+version = "0.10.5"
 
 [[deps.ImageIO]]
 deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs", "WebP"]
@@ -564,9 +572,9 @@ version = "0.6.9"
 
 [[deps.ImageMetadata]]
 deps = ["AxisArrays", "ImageAxes", "ImageBase", "ImageCore"]
-git-tree-sha1 = "355e2b974f2e3212a75dfb60519de21361ad3cb7"
+git-tree-sha1 = "2a81c3897be6fbcde0802a0ebe6796d0562f63ec"
 uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
-version = "0.9.9"
+version = "0.9.10"
 
 [[deps.Imath_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -586,9 +594,9 @@ version = "0.1.5"
 
 [[deps.IntelOpenMP_jll]]
 deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"]
-git-tree-sha1 = "10bd689145d2c3b2a9844005d01087cc1194e79e"
+git-tree-sha1 = "0f14a5456bdc6b9731a5682f439a672750a09e48"
 uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
-version = "2024.2.1+0"
+version = "2025.0.4+0"
 
 [[deps.InteractiveUtils]]
 deps = ["Markdown"]
@@ -606,10 +614,10 @@ weakdeps = ["Unitful"]
     InterpolationsUnitfulExt = "Unitful"
 
 [[deps.IntervalArithmetic]]
-deps = ["CRlibm_jll", "LinearAlgebra", "MacroTools", "RoundingEmulator"]
-git-tree-sha1 = "24c095b1ec7ee58b936985d31d5df92f9b9cfebb"
+deps = ["CRlibm_jll", "LinearAlgebra", "MacroTools", "OpenBLASConsistentFPCSR_jll", "RoundingEmulator"]
+git-tree-sha1 = "dfbf101df925acf1caa3b15a00b574887cd8472d"
 uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
-version = "0.22.19"
+version = "0.22.26"
 
     [deps.IntervalArithmetic.extensions]
     IntervalArithmeticDiffRulesExt = "DiffRules"
@@ -645,9 +653,9 @@ weakdeps = ["Dates", "Test"]
     InverseFunctionsTestExt = "Test"
 
 [[deps.IrrationalConstants]]
-git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2"
+git-tree-sha1 = "e2222959fbc6c19554dc15174c81bf7bf3aa691c"
 uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
-version = "0.2.2"
+version = "0.2.4"
 
 [[deps.Isoband]]
 deps = ["isoband_jll"]
@@ -667,9 +675,9 @@ version = "1.0.0"
 
 [[deps.JLLWrappers]]
 deps = ["Artifacts", "Preferences"]
-git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b"
+git-tree-sha1 = "a007feb38b422fbdab534406aeca1b86823cb4d6"
 uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
-version = "1.6.1"
+version = "1.7.0"
 
 [[deps.JSON]]
 deps = ["Dates", "Mmap", "Parsers", "Unicode"]
@@ -679,9 +687,9 @@ version = "0.21.4"
 
 [[deps.JSON3]]
 deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"]
-git-tree-sha1 = "1d322381ef7b087548321d3f878cb4c9bd8f8f9b"
+git-tree-sha1 = "196b41e5a854b387d99e5ede2de3fcb4d0422aae"
 uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
-version = "1.14.1"
+version = "1.14.2"
 
     [deps.JSON3.extensions]
     JSON3ArrowExt = ["ArrowTypes"]
@@ -691,15 +699,15 @@ version = "1.14.1"
 
 [[deps.JpegTurbo]]
 deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"]
-git-tree-sha1 = "fa6d0bcff8583bac20f1ffa708c3913ca605c611"
+git-tree-sha1 = "9496de8fb52c224a2e3f9ff403947674517317d9"
 uuid = "b835a17e-a41a-41e7-81f0-2f016b05efe0"
-version = "0.1.5"
+version = "0.1.6"
 
 [[deps.JpegTurbo_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "25ee0be4d43d0269027024d75a24c24d6c6e590c"
+git-tree-sha1 = "eac1206917768cb54957c65a615460d87b455fc1"
 uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
-version = "3.0.4+0"
+version = "3.1.1+0"
 
 [[deps.KernelDensity]]
 deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"]
@@ -715,9 +723,9 @@ version = "3.100.2+0"
 
 [[deps.LERC_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "36bdbc52f13a7d1dcb0f3cd694e01677a515655b"
+git-tree-sha1 = "aaafe88dccbd957a8d82f7d05be9b69172e0cee3"
 uuid = "88015f11-f218-50d7-93a8-a6af411a945d"
-version = "4.0.0+0"
+version = "4.0.1+0"
 
 [[deps.LLVMOpenMP_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -727,9 +735,9 @@ version = "18.1.7+0"
 
 [[deps.LZO_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "854a9c268c43b77b0a27f22d7fab8d33cdb3a731"
+git-tree-sha1 = "1c602b1127f4751facb671441ca72715cc95938a"
 uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
-version = "2.10.2+1"
+version = "2.10.3+0"
 
 [[deps.LaTeXStrings]]
 git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
@@ -777,9 +785,9 @@ version = "1.11.0"
 
 [[deps.Libffi_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290"
+git-tree-sha1 = "27ecae93dd25ee0909666e6835051dd684cc035e"
 uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
-version = "3.2.2+1"
+version = "3.2.2+2"
 
 [[deps.Libgcrypt_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"]
@@ -788,40 +796,40 @@ uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
 version = "1.11.0+0"
 
 [[deps.Libglvnd_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
-git-tree-sha1 = "6f73d1dd803986947b2c750138528a999a6c7733"
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"]
+git-tree-sha1 = "ff3b4b9d35de638936a525ecd36e86a8bb919d11"
 uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
-version = "1.6.0+0"
+version = "1.7.0+0"
 
 [[deps.Libgpg_error_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "c6ce1e19f3aec9b59186bdf06cdf3c4fc5f5f3e6"
+git-tree-sha1 = "df37206100d39f79b3376afb6b9cee4970041c61"
 uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
-version = "1.50.0+0"
+version = "1.51.1+0"
 
 [[deps.Libiconv_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "61dfdba58e585066d8bce214c5a51eaa0539f269"
+git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809"
 uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
-version = "1.17.0+1"
+version = "1.18.0+0"
 
 [[deps.Libmount_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e"
+git-tree-sha1 = "89211ea35d9df5831fca5d33552c02bd33878419"
 uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
-version = "2.40.1+0"
+version = "2.40.3+0"
 
 [[deps.Libtiff_jll]]
 deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"]
-git-tree-sha1 = "b404131d06f7886402758c9ce2214b636eb4d54a"
+git-tree-sha1 = "4ab7581296671007fc33f07a721631b8855f4b1d"
 uuid = "89763e89-9b03-5906-acba-b20f662cd828"
-version = "4.7.0+0"
+version = "4.7.1+0"
 
 [[deps.Libuuid_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807"
+git-tree-sha1 = "e888ad02ce716b319e6bdb985d2ef300e7089889"
 uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
-version = "2.40.1+0"
+version = "2.40.3+0"
 
 [[deps.LinearAlgebra]]
 deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
@@ -830,9 +838,9 @@ version = "1.11.0"
 
 [[deps.LogExpFunctions]]
 deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
-git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea"
+git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f"
 uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
-version = "0.3.28"
+version = "0.3.29"
 
     [deps.LogExpFunctions.extensions]
     LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
@@ -860,17 +868,22 @@ git-tree-sha1 = "1d2dd9b186742b0f317f2530ddcbf00eebb18e96"
 uuid = "23992714-dd62-5051-b70f-ba57cb901cac"
 version = "0.10.7"
 
+[[deps.MIMEs]]
+git-tree-sha1 = "c64d943587f7187e751162b3b84445bbbd79f691"
+uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
+version = "1.1.0"
+
 [[deps.MKL_jll]]
 deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"]
-git-tree-sha1 = "f046ccd0c6db2832a9f639e2c669c6fe867e5f4f"
+git-tree-sha1 = "5de60bc6cb3899cd318d80d627560fae2e2d99ae"
 uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
-version = "2024.2.0+0"
+version = "2025.0.1+1"
 
 [[deps.MPICH_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
-git-tree-sha1 = "7715e65c47ba3941c502bffb7f266a41a7f54423"
+git-tree-sha1 = "3aa3210044138a1749dbd350a9ba8680869eb503"
 uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4"
-version = "4.2.3+0"
+version = "4.3.0+1"
 
 [[deps.MPIPreferences]]
 deps = ["Libdl", "Preferences"]
@@ -880,27 +893,26 @@ version = "0.1.11"
 
 [[deps.MPItrampoline_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
-git-tree-sha1 = "70e830dab5d0775183c99fc75e4c24c614ed7142"
+git-tree-sha1 = "ff91ca13c7c472cef700f301c8d752bc2aaff1a8"
 uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748"
-version = "5.5.1+0"
+version = "5.5.3+0"
 
 [[deps.MacroTools]]
-deps = ["Markdown", "Random"]
-git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df"
+git-tree-sha1 = "72aebe0b5051e5143a079a4685a46da330a40472"
 uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
-version = "0.5.13"
+version = "0.5.15"
 
 [[deps.Makie]]
-deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageBase", "ImageIO", "InteractiveUtils", "Interpolations", "IntervalSets", "InverseFunctions", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"]
-git-tree-sha1 = "f7907907eb914138cc9e9ee66ab46f7a9efac8e8"
+deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageBase", "ImageIO", "InteractiveUtils", "Interpolations", "IntervalSets", "InverseFunctions", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "PNGFiles", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"]
+git-tree-sha1 = "e64b545d25e05a609521bfc36724baa072bfd31a"
 uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
-version = "0.21.15"
+version = "0.22.2"
 
 [[deps.MakieCore]]
 deps = ["ColorTypes", "GeometryBasics", "IntervalSets", "Observables"]
-git-tree-sha1 = "4604f03e5b057e8e62a95a44929cafc9585b0fe9"
+git-tree-sha1 = "605d6e8f2b7eba7f5bc6a16d297475075d5ea775"
 uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b"
-version = "0.8.9"
+version = "0.9.1"
 
 [[deps.MappedArrays]]
 git-tree-sha1 = "2dab0221fe2b0f2cb6754eaa743cc266339f527e"
@@ -937,9 +949,9 @@ version = "0.29.0"
 
 [[deps.MicrosoftMPI_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "f12a29c4400ba812841c6ace3f4efbb6dbb3ba01"
+git-tree-sha1 = "bc95bf4149bf535c09602e3acdf950d9b4376227"
 uuid = "9237b28f-5490-5468-be7b-bb81f5f5e6cf"
-version = "10.1.4+2"
+version = "10.1.4+3"
 
 [[deps.Missings]]
 deps = ["DataAPI"]
@@ -969,9 +981,9 @@ version = "1.2.1"
 
 [[deps.NearestNeighbors]]
 deps = ["Distances", "StaticArrays"]
-git-tree-sha1 = "3cebfc94a0754cc329ebc3bab1e6c89621e791ad"
+git-tree-sha1 = "8a3271d8309285f4db73b4f662b1b290c715e85e"
 uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
-version = "0.4.20"
+version = "0.4.21"
 
 [[deps.Netpbm]]
 deps = ["FileIO", "ImageCore", "ImageMetadata"]
@@ -985,11 +997,11 @@ version = "1.2.0"
 
 [[deps.NyxWidgets]]
 deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
-git-tree-sha1 = "e18ab14817871c54419e4cef12f9fc4dc589f6fe"
+git-tree-sha1 = "d5addf940e4265b3ca2a6fe29b8ff24bf3b13b98"
 repo-rev = "main"
 repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
 uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
-version = "0.2.0"
+version = "0.2.1"
 
 [[deps.Observables]]
 git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
@@ -1005,9 +1017,9 @@ uuid = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
 version = "0.2.4"
 
 [[deps.OffsetArrays]]
-git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e"
+git-tree-sha1 = "a414039192a155fb38c4599a60110f0018c6ec82"
 uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
-version = "1.14.1"
+version = "1.16.0"
 weakdeps = ["Adapt"]
 
     [deps.OffsetArrays.extensions]
@@ -1019,6 +1031,12 @@ git-tree-sha1 = "887579a3eb005446d514ab7aeac5d1d027658b8f"
 uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
 version = "1.3.5+1"
 
+[[deps.OpenBLASConsistentFPCSR_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "567515ca155d0020a45b05175449b499c63e7015"
+uuid = "6cdc7f73-28fd-5e50-80fb-958a8875b1af"
+version = "0.3.29+0"
+
 [[deps.OpenBLAS_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
 uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
@@ -1026,9 +1044,9 @@ version = "0.3.27+1"
 
 [[deps.OpenEXR]]
 deps = ["Colors", "FileIO", "OpenEXR_jll"]
-git-tree-sha1 = "327f53360fdb54df7ecd01e96ef1983536d1e633"
+git-tree-sha1 = "97db9e07fe2091882c765380ef58ec553074e9c7"
 uuid = "52e1d378-f018-4a11-a4be-720524705ac7"
-version = "0.3.2"
+version = "0.3.3"
 
 [[deps.OpenEXR_jll]]
 deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
@@ -1039,13 +1057,13 @@ version = "3.2.4+0"
 [[deps.OpenLibm_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
-version = "0.8.1+2"
+version = "0.8.1+4"
 
 [[deps.OpenMPI_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML", "Zlib_jll"]
-git-tree-sha1 = "bfce6d523861a6c562721b262c0d1aaeead2647f"
+git-tree-sha1 = "da913f03f17b449951e0461da960229d4a3d1a8c"
 uuid = "fe0851c0-eecd-5654-98d4-656369965a5c"
-version = "5.0.5+0"
+version = "5.0.7+1"
 
 [[deps.OpenSSL]]
 deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
@@ -1055,15 +1073,15 @@ version = "1.4.3"
 
 [[deps.OpenSSL_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10"
+git-tree-sha1 = "a9697f1d06cc3eb3fb3ad49cc67f2cfabaac31ea"
 uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
-version = "3.0.15+1"
+version = "3.0.16+0"
 
 [[deps.OpenSpecFun_jll]]
-deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335"
 uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
-version = "0.5.5+0"
+version = "0.5.6+0"
 
 [[deps.Opus_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -1072,9 +1090,15 @@ uuid = "91d4177d-7536-5919-b921-800302f37372"
 version = "1.3.3+0"
 
 [[deps.OrderedCollections]]
-git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5"
+git-tree-sha1 = "cc4054e898b852042d7b503313f7ad03de99c3dd"
 uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
-version = "1.6.3"
+version = "1.8.0"
+
+[[deps.Oxygen]]
+deps = ["DataStructures", "Dates", "HTTP", "JSON3", "MIMEs", "Reexport", "RelocatableFolders", "Requires", "Sockets", "Statistics", "StructTypes"]
+git-tree-sha1 = "2ad010b0de6172faf1d09ed5e0837eb0b7355bd8"
+uuid = "df9a0d86-3283-4920-82dc-4555fc0d1d8b"
+version = "1.5.16"
 
 [[deps.PCRE2_jll]]
 deps = ["Artifacts", "Libdl"]
@@ -1083,21 +1107,21 @@ version = "10.42.0+1"
 
 [[deps.PDMats]]
 deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
-git-tree-sha1 = "949347156c25054de2db3b166c52ac4728cbad65"
+git-tree-sha1 = "48566789a6d5f6492688279e22445002d171cf76"
 uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
-version = "0.11.31"
+version = "0.11.33"
 
 [[deps.PNGFiles]]
 deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"]
-git-tree-sha1 = "67186a2bc9a90f9f85ff3cc8277868961fb57cbd"
+git-tree-sha1 = "cf181f0b1e6a18dfeb0ee8acc4a9d1672499626c"
 uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
-version = "0.4.3"
+version = "0.4.4"
 
 [[deps.Packing]]
 deps = ["GeometryBasics"]
-git-tree-sha1 = "ec3edfe723df33528e085e632414499f26650501"
+git-tree-sha1 = "bc5bf2ea3d5351edf285a06b0016788a121ce92c"
 uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566"
-version = "0.5.0"
+version = "0.5.1"
 
 [[deps.PaddedViews]]
 deps = ["OffsetArrays"]
@@ -1113,9 +1137,9 @@ version = "2.8.1"
 
 [[deps.Pixman_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"]
-git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b"
+git-tree-sha1 = "db76b1ecd5e9715f3d043cec13b2ec93ce015d53"
 uuid = "30392449-352a-5448-841d-b1acce4e97dc"
-version = "0.43.4+0"
+version = "0.44.2+0"
 
 [[deps.Pkg]]
 deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"]
@@ -1136,7 +1160,7 @@ version = "0.3.3"
 deps = ["DelimitedFiles", "HDF5", "JSON3", "LinearAlgebra", "MAT", "Meshes", "OrderedCollections", "Random", "SHA", "StaticArrays", "Statistics", "StatsBase", "StructTypes"]
 git-tree-sha1 = "d964d040e319fe3bd9140e5bf91d648de6acc96f"
 repo-rev = "main"
-repo-url = "https://gitlab.pasteur.fr/nyx/planarlarvae.jl"
+repo-url = "https://gitlab.pasteur.fr/nyx/PlanarLarvae.jl"
 uuid = "c2615984-ef14-4d40-b148-916c85b43307"
 version = "0.16.0"
 
@@ -1175,21 +1199,21 @@ uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
 version = "1.10.2"
 
 [[deps.PtrArrays]]
-git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f"
+git-tree-sha1 = "1d36ef11a9aaf1e8b74dacc6a731dd1de8fd493d"
 uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
-version = "1.2.1"
+version = "1.3.0"
 
 [[deps.QOI]]
 deps = ["ColorTypes", "FileIO", "FixedPointNumbers"]
-git-tree-sha1 = "18e8f4d1426e965c7b532ddd260599e1510d26ce"
+git-tree-sha1 = "8b3fc30bc0390abdce15f8822c889f669baed73d"
 uuid = "4b34888f-f399-49d4-9bb3-47ed5cae4e65"
-version = "1.0.0"
+version = "1.0.1"
 
 [[deps.QuadGK]]
 deps = ["DataStructures", "LinearAlgebra"]
-git-tree-sha1 = "cda3b045cf9ef07a08ad46731f5a3165e56cf3da"
+git-tree-sha1 = "9da16da70037ba9d701192e27befedefb91ec284"
 uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
-version = "2.11.1"
+version = "2.11.2"
 
     [deps.QuadGK.extensions]
     QuadGKEnzymeExt = "Enzyme"
@@ -1253,9 +1277,9 @@ version = "1.0.1"
 
 [[deps.Requires]]
 deps = ["UUIDs"]
-git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
+git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64"
 uuid = "ae029012-a4dd-5104-9daa-d747884805df"
-version = "1.3.0"
+version = "1.3.1"
 
 [[deps.Rmath]]
 deps = ["Random", "Rmath_jll"]
@@ -1290,9 +1314,9 @@ version = "0.7.0"
 
 [[deps.SIMD]]
 deps = ["PrecompileTools"]
-git-tree-sha1 = "98ca7c29edd6fc79cd74c61accb7010a4e7aee33"
+git-tree-sha1 = "fea870727142270bdf7624ad675901a1ee3b4c87"
 uuid = "fdea26ae-647d-5447-a871-4b548cad5224"
-version = "3.6.0"
+version = "3.7.1"
 
 [[deps.Scratch]]
 deps = ["Dates"]
@@ -1305,10 +1329,10 @@ uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
 version = "1.11.0"
 
 [[deps.ShaderAbstractions]]
-deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays", "StructArrays", "Tables"]
-git-tree-sha1 = "79123bc60c5507f035e6d1d9e563bb2971954ec8"
+deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays"]
+git-tree-sha1 = "818554664a2e01fc3784becb2eb3a82326a604b6"
 uuid = "65257c39-d410-5151-9873-9b3e5be5013e"
-version = "0.4.1"
+version = "0.5.0"
 
 [[deps.SharedArrays]]
 deps = ["Distributed", "Mmap", "Random", "Serialization"]
@@ -1361,9 +1385,9 @@ version = "1.11.0"
 
 [[deps.SpecialFunctions]]
 deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
-git-tree-sha1 = "2f5d4697f21388cbe1ff299430dd169ef97d7e14"
+git-tree-sha1 = "64cca0c26b4f31ba18f13f6c12af7c85f478cfde"
 uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
-version = "2.4.0"
+version = "2.5.0"
 weakdeps = ["ChainRulesCore"]
 
     [deps.SpecialFunctions.extensions]
@@ -1383,9 +1407,9 @@ version = "0.1.1"
 
 [[deps.StaticArrays]]
 deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"]
-git-tree-sha1 = "777657803913ffc7e8cc20f0fd04b634f871af8f"
+git-tree-sha1 = "0feb6b9031bd5c51f9072393eb5ab3efd31bf9e4"
 uuid = "90137ffa-7385-5640-81b9-e52037218182"
-version = "1.9.8"
+version = "1.9.13"
 weakdeps = ["ChainRulesCore", "Statistics"]
 
     [deps.StaticArrays.extensions]
@@ -1414,10 +1438,10 @@ uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
 version = "1.7.0"
 
 [[deps.StatsBase]]
-deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
-git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21"
+deps = ["AliasTables", "DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
+git-tree-sha1 = "29321314c920c26684834965ec2ce0dacc9cf8e5"
 uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
-version = "0.34.3"
+version = "0.34.4"
 
 [[deps.StatsFuns]]
 deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
@@ -1432,19 +1456,22 @@ weakdeps = ["ChainRulesCore", "InverseFunctions"]
 
 [[deps.StructArrays]]
 deps = ["ConstructionBase", "DataAPI", "Tables"]
-git-tree-sha1 = "f4dc295e983502292c4c3f951dbb4e985e35b3be"
+git-tree-sha1 = "5a3a31c41e15a1e042d60f2f4942adccba05d3c9"
 uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
-version = "0.6.18"
+version = "0.7.0"
 
     [deps.StructArrays.extensions]
     StructArraysAdaptExt = "Adapt"
-    StructArraysGPUArraysCoreExt = "GPUArraysCore"
+    StructArraysGPUArraysCoreExt = ["GPUArraysCore", "KernelAbstractions"]
+    StructArraysLinearAlgebraExt = "LinearAlgebra"
     StructArraysSparseArraysExt = "SparseArrays"
     StructArraysStaticArraysExt = "StaticArrays"
 
     [deps.StructArrays.weakdeps]
     Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
     GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527"
+    KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
+    LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
     SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
     StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
 
@@ -1510,15 +1537,15 @@ version = "2.1.1"
 deps = ["Observables"]
 git-tree-sha1 = "0589ec7397374678942cae9aa356b4bb6c1e9bf4"
 repo-rev = "main"
-repo-url = "https://gitlab.com/dbc-nyx/tidyobservables.jl"
+repo-url = "https://gitlab.com/dbc-nyx/TidyObservables.jl"
 uuid = "c8131bbd-73a8-4254-a42d-d5d4c5febb31"
 version = "0.1.1"
 
 [[deps.TiffImages]]
 deps = ["ColorTypes", "DataStructures", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "Mmap", "OffsetArrays", "PkgVersion", "ProgressMeter", "SIMD", "UUIDs"]
-git-tree-sha1 = "6ee0c220d0aecad18792c277ae358129cc50a475"
+git-tree-sha1 = "f21231b166166bebc73b99cea236071eb047525b"
 uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
-version = "0.11.0"
+version = "0.11.3"
 
 [[deps.TranscodingStreams]]
 git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742"
@@ -1537,9 +1564,9 @@ uuid = "981d1d27-644d-49a2-9326-4793e63143c3"
 version = "0.1.0"
 
 [[deps.URIs]]
-git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
+git-tree-sha1 = "cbbebadbcc76c5ca1cc4b4f3b0614b3e603b5000"
 uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
-version = "1.5.1"
+version = "1.5.2"
 
 [[deps.UUIDs]]
 deps = ["Random", "SHA"]
@@ -1558,9 +1585,9 @@ version = "0.4.1"
 
 [[deps.Unitful]]
 deps = ["Dates", "LinearAlgebra", "Random"]
-git-tree-sha1 = "d95fe458f26209c66a187b1114df96fd70839efd"
+git-tree-sha1 = "c0667a8e676c53d390a09dc6870b3d8d6650e2bf"
 uuid = "1986cc42-f94f-5a68-af5c-568840ba703d"
-version = "1.21.0"
+version = "1.22.0"
 weakdeps = ["ConstructionBase", "InverseFunctions"]
 
     [deps.Unitful.extensions]
@@ -1569,15 +1596,15 @@ weakdeps = ["ConstructionBase", "InverseFunctions"]
 
 [[deps.WGLMakie]]
 deps = ["Bonito", "Colors", "FileIO", "FreeTypeAbstraction", "GeometryBasics", "Hyperscript", "LinearAlgebra", "Makie", "Observables", "PNGFiles", "PrecompileTools", "RelocatableFolders", "ShaderAbstractions", "StaticArrays"]
-git-tree-sha1 = "13cab94d885d7760d487d7e2f30c2d6df4643880"
+git-tree-sha1 = "94de96cef3e4b9ec09096f6e5e9814041d59300c"
 uuid = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"
-version = "0.10.15"
+version = "0.11.2"
 
 [[deps.WebP]]
 deps = ["CEnum", "ColorTypes", "FileIO", "FixedPointNumbers", "ImageCore", "libwebp_jll"]
-git-tree-sha1 = "f1f6d497ff84039deeb37f264396dac0c2250497"
+git-tree-sha1 = "aa1ca3c47f119fbdae8770c29820e5e6119b83f2"
 uuid = "e3aaa7dc-3e4b-44e0-be63-ffb868ccd7c1"
-version = "0.1.2"
+version = "0.1.3"
 
 [[deps.WidgetsBase]]
 deps = ["Observables"]
@@ -1593,69 +1620,69 @@ version = "1.0.0"
 
 [[deps.XML2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"]
-git-tree-sha1 = "6a451c6f33a176150f315726eba8b92fbfdb9ae7"
+git-tree-sha1 = "b8b243e47228b4a3877f1dd6aee0c5d56db7fcf4"
 uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
-version = "2.13.4+0"
+version = "2.13.6+1"
 
 [[deps.XSLT_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "XML2_jll", "Zlib_jll"]
-git-tree-sha1 = "a54ee957f4c86b526460a720dbc882fa5edcbefc"
+git-tree-sha1 = "7d1671acbe47ac88e981868a078bd6b4e27c5191"
 uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
-version = "1.1.41+0"
+version = "1.1.42+0"
 
 [[deps.XZ_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "15e637a697345f6743674f1322beefbc5dcd5cfc"
+git-tree-sha1 = "56c6604ec8b2d82cc4cfe01aa03b00426aac7e1f"
 uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800"
-version = "5.6.3+0"
+version = "5.6.4+1"
 
 [[deps.Xorg_libX11_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
-git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495"
+git-tree-sha1 = "9dafcee1d24c4f024e7edc92603cedba72118283"
 uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
-version = "1.8.6+0"
+version = "1.8.6+3"
 
 [[deps.Xorg_libXau_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8"
+git-tree-sha1 = "e9216fdcd8514b7072b43653874fd688e4c6c003"
 uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
-version = "1.0.11+0"
+version = "1.0.12+0"
 
 [[deps.Xorg_libXdmcp_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7"
+git-tree-sha1 = "89799ae67c17caa5b3b5a19b8469eeee474377db"
 uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
-version = "1.1.4+0"
+version = "1.1.5+0"
 
 [[deps.Xorg_libXext_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"]
-git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85"
+git-tree-sha1 = "d7155fea91a4123ef59f42c4afb5ab3b4ca95058"
 uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
-version = "1.3.6+0"
+version = "1.3.6+3"
 
 [[deps.Xorg_libXrender_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"]
-git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe"
+git-tree-sha1 = "a490c6212a0e90d2d55111ac956f7c4fa9c277a6"
 uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
-version = "0.9.11+0"
+version = "0.9.11+1"
 
 [[deps.Xorg_libpthread_stubs_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9"
+git-tree-sha1 = "c57201109a9e4c0585b208bb408bc41d205ac4e9"
 uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
-version = "0.1.1+0"
+version = "0.1.2+0"
 
 [[deps.Xorg_libxcb_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
-git-tree-sha1 = "bcd466676fef0878338c61e655629fa7bbc69d8e"
+git-tree-sha1 = "1a74296303b6524a0472a8cb12d3d87a78eb3612"
 uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
-version = "1.17.0+0"
+version = "1.17.0+3"
 
 [[deps.Xorg_xtrans_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77"
+git-tree-sha1 = "6dba04dbfb72ae3ebe5418ba33d087ba8aa8cb00"
 uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
-version = "1.5.0+0"
+version = "1.5.1+0"
 
 [[deps.Zlib_jll]]
 deps = ["Libdl"]
@@ -1664,9 +1691,9 @@ version = "1.2.13+1"
 
 [[deps.Zstd_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b"
+git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308"
 uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
-version = "1.5.6+1"
+version = "1.5.7+1"
 
 [[deps.isoband_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
@@ -1676,15 +1703,15 @@ version = "0.2.3+0"
 
 [[deps.libaec_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "46bf7be2917b59b761247be3f317ddf75e50e997"
+git-tree-sha1 = "f5733a5a9047722470b95a81e1b172383971105c"
 uuid = "477f73a3-ac25-53e9-8cc3-50b2fa2566f0"
-version = "1.1.2+0"
+version = "1.1.3+0"
 
 [[deps.libaom_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "1827acba325fdcdf1d2647fc8d5301dd9ba43a9d"
+git-tree-sha1 = "522c1df09d05a71785765d19c9524661234738e9"
 uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b"
-version = "3.9.0+0"
+version = "3.11.0+0"
 
 [[deps.libass_jll]]
 deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
@@ -1705,15 +1732,15 @@ version = "2.0.3+0"
 
 [[deps.libpng_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
-git-tree-sha1 = "b70c870239dc3d7bc094eb2d6be9b73d27bef280"
+git-tree-sha1 = "068dfe202b0a05b8332f1e8e6b4080684b9c7700"
 uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
-version = "1.6.44+0"
+version = "1.6.47+0"
 
 [[deps.libsixel_jll]]
-deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "libpng_jll"]
-git-tree-sha1 = "7dfa0fd9c783d3d0cc43ea1af53d69ba45c447df"
+deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "libpng_jll"]
+git-tree-sha1 = "c1733e347283df07689d71d61e14be986e49e47a"
 uuid = "075b6546-f08a-558a-be8f-8157d0f608a5"
-version = "1.10.3+1"
+version = "1.10.5+0"
 
 [[deps.libvorbis_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
@@ -1723,9 +1750,9 @@ version = "1.3.7+2"
 
 [[deps.libwebp_jll]]
 deps = ["Artifacts", "Giflib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libglvnd_jll", "Libtiff_jll", "libpng_jll"]
-git-tree-sha1 = "ccbb625a89ec6195856a50aa2b668a5c08712c94"
+git-tree-sha1 = "d2408cac540942921e7bd77272c32e58c33d8a77"
 uuid = "c5f90fcd-3b7e-5836-afba-fc50a0988cb2"
-version = "1.4.0+0"
+version = "1.5.0+0"
 
 [[deps.nghttp2_jll]]
 deps = ["Artifacts", "Libdl"]
@@ -1734,9 +1761,9 @@ version = "1.59.0+0"
 
 [[deps.oneTBB_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "7d0ea0f4895ef2f5cb83645fa689e52cb55cf493"
+git-tree-sha1 = "d5a767a3bb77135a99e433afe0eb14cd7f6914c3"
 uuid = "1317d2d5-d96f-522e-a858-c73665f53c3e"
-version = "2021.12.0+0"
+version = "2022.0.0+0"
 
 [[deps.p7zip_jll]]
 deps = ["Artifacts", "Libdl"]
@@ -1745,9 +1772,9 @@ version = "17.4.0+2"
 
 [[deps.x264_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "35976a1216d6c066ea32cba2150c4fa682b276fc"
+git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7"
 uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
-version = "10164.0.0+0"
+version = "10164.0.1+0"
 
 [[deps.x265_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
diff --git a/Manifest.toml b/Manifest.toml
index af56fc6..5b90db9 100644
--- a/Manifest.toml
+++ b/Manifest.toml
@@ -22,12 +22,13 @@ version = "0.4.5"
 
 [[deps.Adapt]]
 deps = ["LinearAlgebra", "Requires"]
-git-tree-sha1 = "50c3c56a52972d78e8be9fd135bfb91c9574c140"
+git-tree-sha1 = "f7817e2e585aa6d924fd714df1e2a84be7896c60"
 uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
-version = "4.1.1"
-weakdeps = ["StaticArrays"]
+version = "4.3.0"
+weakdeps = ["SparseArrays", "StaticArrays"]
 
     [deps.Adapt.extensions]
+    AdaptSparseArraysExt = "SparseArrays"
     AdaptStaticArraysExt = "StaticArrays"
 
 [[deps.AdaptivePredicates]]
@@ -43,9 +44,9 @@ version = "1.1.3"
 
 [[deps.Animations]]
 deps = ["Colors"]
-git-tree-sha1 = "e81c509d2c8e49592413bfb0bb3b08150056c79d"
+git-tree-sha1 = "e092fa223bf66a3c41f9c022bd074d916dc303e7"
 uuid = "27a7e980-b3e6-11e9-2bcd-0b925532e340"
-version = "0.4.1"
+version = "0.4.2"
 
 [[deps.ArgTools]]
 uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
@@ -97,10 +98,10 @@ uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
 version = "1.2.2"
 
 [[deps.Bzip2_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "8873e196c2eb87962a2048b3b8e08946535864a1"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e"
 uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
-version = "1.0.8+2"
+version = "1.0.9+0"
 
 [[deps.CEnum]]
 git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
@@ -118,15 +119,15 @@ version = "1.0.1+0"
 
 [[deps.Cairo_jll]]
 deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
-git-tree-sha1 = "009060c9a6168704143100f36ab08f06c2af4642"
+git-tree-sha1 = "2ac646d71d0d24b44f3f8c84da8c9f4d70fb67df"
 uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
-version = "1.18.2+1"
+version = "1.18.4+0"
 
 [[deps.ChainRulesCore]]
 deps = ["Compat", "LinearAlgebra"]
-git-tree-sha1 = "3e4b134270b372f2ed4d4d0e936aabaefc1802bc"
+git-tree-sha1 = "1713c74e00545bfe14605d2a2be1712de8fbcb58"
 uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
-version = "1.25.0"
+version = "1.25.1"
 weakdeps = ["SparseArrays"]
 
     [deps.ChainRulesCore.extensions]
@@ -140,33 +141,39 @@ version = "1.4.0"
 
 [[deps.CodecZlib]]
 deps = ["TranscodingStreams", "Zlib_jll"]
-git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759"
+git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9"
 uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
-version = "0.7.6"
+version = "0.7.8"
 
 [[deps.ColorBrewer]]
-deps = ["Colors", "JSON", "Test"]
-git-tree-sha1 = "61c5334f33d91e570e1d0c3eb5465835242582c4"
+deps = ["Colors", "JSON"]
+git-tree-sha1 = "e771a63cc8b539eca78c85b0cabd9233d6c8f06f"
 uuid = "a2cac450-b92f-5266-8821-25eda20663c8"
-version = "0.4.0"
+version = "0.4.1"
 
 [[deps.ColorSchemes]]
 deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"]
-git-tree-sha1 = "c785dfb1b3bfddd1da557e861b919819b82bbe5b"
+git-tree-sha1 = "403f2d8e209681fcbd9468a8514efff3ea08452e"
 uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
-version = "3.27.1"
+version = "3.29.0"
 
 [[deps.ColorTypes]]
 deps = ["FixedPointNumbers", "Random"]
-git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d"
+git-tree-sha1 = "c7acce7a7e1078a20a285211dd73cd3941a871d6"
 uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
-version = "0.11.5"
+version = "0.12.0"
+
+    [deps.ColorTypes.extensions]
+    StyledStringsExt = "StyledStrings"
+
+    [deps.ColorTypes.weakdeps]
+    StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
 
 [[deps.ColorVectorSpace]]
 deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"]
-git-tree-sha1 = "a1f44953f2382ebb937d60dafbe2deea4bd23249"
+git-tree-sha1 = "8b3b6f87ce8f65a2b4f857528fd8d70086cd72b1"
 uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
-version = "0.10.0"
+version = "0.11.0"
 weakdeps = ["SpecialFunctions"]
 
     [deps.ColorVectorSpace.extensions]
@@ -174,9 +181,9 @@ weakdeps = ["SpecialFunctions"]
 
 [[deps.Colors]]
 deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
-git-tree-sha1 = "362a287c3aa50601b0bc359053d5c2468f0e7ce0"
+git-tree-sha1 = "64e15186f0aa277e174aa81798f7eb8598e0157e"
 uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
-version = "0.12.11"
+version = "0.13.0"
 
 [[deps.Compat]]
 deps = ["TOML", "UUIDs"]
@@ -195,9 +202,9 @@ version = "1.1.1+0"
 
 [[deps.ConcurrentUtilities]]
 deps = ["Serialization", "Sockets"]
-git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1"
+git-tree-sha1 = "d9d26935a0bcffc87d2613ce14c527c99fc543fd"
 uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
-version = "2.4.2"
+version = "2.5.0"
 
 [[deps.ConstructionBase]]
 git-tree-sha1 = "76219f1ed5771adbb096743bff43fb5fdd4c1157"
@@ -222,9 +229,9 @@ version = "1.16.0"
 
 [[deps.DataStructures]]
 deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
-git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82"
+git-tree-sha1 = "4e1fe97fdaed23e9dc21d4d664bea76b65fc50a0"
 uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
-version = "0.18.20"
+version = "0.18.22"
 
 [[deps.DataValueInterfaces]]
 git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
@@ -236,10 +243,10 @@ deps = ["Printf"]
 uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
 
 [[deps.DelaunayTriangulation]]
-deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "PrecompileTools", "Random"]
-git-tree-sha1 = "89df54fbe66e5872d91d8c2cd3a375f660c3fd64"
+deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "Random"]
+git-tree-sha1 = "5620ff4ee0084a6ab7097a27ba0c19290200b037"
 uuid = "927a84f5-c5f4-47a5-9785-b46e178433df"
-version = "1.6.1"
+version = "1.6.4"
 
 [[deps.DelimitedFiles]]
 deps = ["Mmap"]
@@ -270,9 +277,9 @@ uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
 
 [[deps.Distributions]]
 deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
-git-tree-sha1 = "3101c32aab536e7a27b1763c0797dba151b899ad"
+git-tree-sha1 = "0b4190661e8a4e51a842070e7dd4fae440ddb7f4"
 uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
-version = "0.25.113"
+version = "0.25.118"
 
     [deps.Distributions.extensions]
     DistributionsChainRulesCoreExt = "ChainRulesCore"
@@ -291,10 +298,9 @@ uuid = "968ba79b-81e4-546f-ab3a-2eecfa62a9db"
 version = "0.5.0"
 
 [[deps.DocStringExtensions]]
-deps = ["LibGit2"]
-git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d"
+git-tree-sha1 = "e7b7e6f178525d17c720ab9c081e4ef04429f860"
 uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
-version = "0.9.3"
+version = "0.9.4"
 
 [[deps.Downloads]]
 deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
@@ -308,9 +314,9 @@ uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
 version = "2.2.4+0"
 
 [[deps.EnumX]]
-git-tree-sha1 = "bdb1942cd4c45e3c678fd11569d5cccd80976237"
+git-tree-sha1 = "bddad79635af6aec424f53ed8aad5d7555dc6f00"
 uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
-version = "1.0.4"
+version = "1.0.5"
 
 [[deps.ExactPredicates]]
 deps = ["IntervalArithmetic", "Random", "StaticArrays"]
@@ -320,20 +326,20 @@ version = "2.2.8"
 
 [[deps.ExceptionUnwrapping]]
 deps = ["Test"]
-git-tree-sha1 = "dcb08a0d93ec0b1cdc4af184b26b591e9695423a"
+git-tree-sha1 = "d36f682e590a83d63d1c7dbd287573764682d12a"
 uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4"
-version = "0.1.10"
+version = "0.1.11"
 
 [[deps.Expat_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7"
+git-tree-sha1 = "d55dffd9ae73ff72f1c0482454dcf2ec6c6c4a63"
 uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
-version = "2.6.2+0"
+version = "2.6.5+0"
 
 [[deps.Extents]]
-git-tree-sha1 = "81023caa0021a41712685887db1fc03db26f41f5"
+git-tree-sha1 = "063512a13dbe9c40d999c439268539aa552d1ae6"
 uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
-version = "0.1.4"
+version = "0.1.5"
 
 [[deps.FFMPEG_jll]]
 deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
@@ -343,21 +349,25 @@ version = "6.1.2+0"
 
 [[deps.FFTW]]
 deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
-git-tree-sha1 = "4820348781ae578893311153d69049a93d05f39d"
+git-tree-sha1 = "7de7c78d681078f027389e067864a8d53bd7c3c9"
 uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
-version = "1.8.0"
+version = "1.8.1"
 
 [[deps.FFTW_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
 git-tree-sha1 = "4d81ed14783ec49ce9f2e168208a12ce1815aa25"
 uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
-version = "3.3.10+1"
+version = "3.3.10+3"
 
 [[deps.FileIO]]
 deps = ["Pkg", "Requires", "UUIDs"]
-git-tree-sha1 = "91e0e5c68d02bcdaae76d3c8ceb4361e8f28d2e9"
+git-tree-sha1 = "b66970a70db13f45b7e57fbda1736e1cf72174ea"
 uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
-version = "1.16.5"
+version = "1.17.0"
+weakdeps = ["HTTP"]
+
+    [deps.FileIO.extensions]
+    HTTPExt = "HTTP"
 
 [[deps.FilePaths]]
 deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"]
@@ -367,9 +377,9 @@ version = "0.8.3"
 
 [[deps.FilePathsBase]]
 deps = ["Compat", "Dates"]
-git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361"
+git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2"
 uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
-version = "0.9.22"
+version = "0.9.24"
 weakdeps = ["Mmap", "Test"]
 
     [deps.FilePathsBase.extensions]
@@ -399,9 +409,9 @@ version = "0.8.5"
 
 [[deps.Fontconfig_jll]]
 deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"]
-git-tree-sha1 = "db16beca600632c95fc8aca29890d83788dd8b23"
+git-tree-sha1 = "21fac3c77d7b5a9fc03b0ec503aa1a6392c34d2b"
 uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
-version = "2.13.96+0"
+version = "2.15.0+0"
 
 [[deps.Format]]
 git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc"
@@ -416,38 +426,38 @@ version = "4.1.1"
 
 [[deps.FreeType2_jll]]
 deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
-git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc"
+git-tree-sha1 = "2c5512e11c791d1baed2049c5652441b28fc6a31"
 uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
-version = "2.13.2+0"
+version = "2.13.4+0"
 
 [[deps.FreeTypeAbstraction]]
 deps = ["ColorVectorSpace", "Colors", "FreeType", "GeometryBasics"]
-git-tree-sha1 = "77e2b094e61d939f9626181ab23d0b76e78f9fd3"
+git-tree-sha1 = "d52e255138ac21be31fa633200b65e4e71d26802"
 uuid = "663a7486-cb36-511b-a19d-713bb74d65c9"
-version = "0.10.5"
+version = "0.10.6"
 
 [[deps.FriBidi_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "1ed150b39aebcc805c26b93a8d0122c940f64ce2"
+git-tree-sha1 = "846f7026a9decf3679419122b49f8a1fdb48d2d5"
 uuid = "559328eb-81f9-559d-9380-de523a88c83c"
-version = "1.0.14+0"
+version = "1.0.16+0"
 
 [[deps.GeoFormatTypes]]
-git-tree-sha1 = "59107c179a586f0fe667024c5eb7033e81333271"
+git-tree-sha1 = "8e233d5167e63d708d41f87597433f59a0f213fe"
 uuid = "68eda718-8dee-11e9-39e7-89f7f65f511f"
-version = "0.4.2"
+version = "0.4.4"
 
 [[deps.GeoInterface]]
-deps = ["Extents", "GeoFormatTypes"]
-git-tree-sha1 = "826b4fd69438d9ce4d2b19de6bc2f970f45f0f88"
+deps = ["DataAPI", "Extents", "GeoFormatTypes"]
+git-tree-sha1 = "294e99f19869d0b0cb71aef92f19d03649d028d5"
 uuid = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
-version = "1.3.8"
+version = "1.4.1"
 
 [[deps.GeometryBasics]]
-deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
-git-tree-sha1 = "b62f2b2d76cee0d61a2ef2b3118cd2a3215d3134"
+deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "PrecompileTools", "Random", "StaticArrays"]
+git-tree-sha1 = "65e3f5c519c3ec6a4c59f4c3ba21b6ff3add95b0"
 uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
-version = "0.4.11"
+version = "0.5.7"
 
 [[deps.Gettext_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
@@ -457,27 +467,27 @@ version = "0.21.0+0"
 
 [[deps.Giflib_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "0224cce99284d997f6880a42ef715a37c99338d1"
+git-tree-sha1 = "6570366d757b50fabae9f4315ad74d2e40c0560a"
 uuid = "59f7168a-df46-5410-90c8-f2779963d0ec"
-version = "5.2.2+0"
+version = "5.2.3+0"
 
 [[deps.Glib_jll]]
 deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"]
-git-tree-sha1 = "674ff0db93fffcd11a3573986e550d66cd4fd71f"
+git-tree-sha1 = "b0036b392358c80d2d2124746c2bf3d48d457938"
 uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
-version = "2.80.5+0"
+version = "2.82.4+0"
 
 [[deps.Graphite2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011"
+git-tree-sha1 = "01979f9b37367603e2848ea225918a3b3861b606"
 uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
-version = "1.3.14+0"
+version = "1.3.14+1"
 
 [[deps.GridLayoutBase]]
 deps = ["GeometryBasics", "InteractiveUtils", "Observables"]
-git-tree-sha1 = "fc713f007cff99ff9e50accba6373624ddd33588"
+git-tree-sha1 = "dc6bed05c15523624909b3953686c5f5ffa10adc"
 uuid = "3955a311-db13-416c-9275-1d80ed98e5e9"
-version = "0.11.0"
+version = "0.11.1"
 
 [[deps.Grisu]]
 git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
@@ -497,10 +507,10 @@ version = "0.17.2"
     MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195"
 
 [[deps.HDF5_jll]]
-deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"]
-git-tree-sha1 = "38c8874692d48d5440d5752d6c74b0c6b0b60739"
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"]
+git-tree-sha1 = "e94f84da9af7ce9c6be049e9067e511e17ff89ec"
 uuid = "0234f1f7-429e-5d53-9886-15a909be8d59"
-version = "1.14.2+1"
+version = "1.14.6+0"
 
 [[deps.HTTP]]
 deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
@@ -510,21 +520,21 @@ version = "1.10.15"
 
 [[deps.HarfBuzz_jll]]
 deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"]
-git-tree-sha1 = "401e4f3f30f43af2c8478fc008da50096ea5240f"
+git-tree-sha1 = "55c53be97790242c29031e5cd45e8ac296dadda3"
 uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
-version = "8.3.1+0"
+version = "8.5.0+0"
 
 [[deps.Hwloc_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "50aedf345a709ab75872f80a2779568dc0bb461b"
+git-tree-sha1 = "f93a9ce66cd89c9ba7a4695a47fd93b4c6bc59fa"
 uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8"
-version = "2.11.2+1"
+version = "2.12.0+0"
 
 [[deps.HypergeometricFunctions]]
 deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
-git-tree-sha1 = "b1c2585431c382e3fe5805874bda6aea90a95de9"
+git-tree-sha1 = "68c173f4f449de5b438ee67ed0c9c748dc31a2ec"
 uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
-version = "0.3.25"
+version = "0.3.28"
 
 [[deps.Hyperscript]]
 deps = ["Test"]
@@ -580,9 +590,9 @@ version = "0.1.5"
 
 [[deps.IntelOpenMP_jll]]
 deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"]
-git-tree-sha1 = "10bd689145d2c3b2a9844005d01087cc1194e79e"
+git-tree-sha1 = "0f14a5456bdc6b9731a5682f439a672750a09e48"
 uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
-version = "2024.2.1+0"
+version = "2025.0.4+0"
 
 [[deps.InteractiveUtils]]
 deps = ["Markdown"]
@@ -599,10 +609,10 @@ weakdeps = ["Unitful"]
     InterpolationsUnitfulExt = "Unitful"
 
 [[deps.IntervalArithmetic]]
-deps = ["CRlibm_jll", "LinearAlgebra", "MacroTools", "RoundingEmulator"]
-git-tree-sha1 = "24c095b1ec7ee58b936985d31d5df92f9b9cfebb"
+deps = ["CRlibm_jll", "LinearAlgebra", "MacroTools", "OpenBLASConsistentFPCSR_jll", "RoundingEmulator"]
+git-tree-sha1 = "dfbf101df925acf1caa3b15a00b574887cd8472d"
 uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
-version = "0.22.19"
+version = "0.22.26"
 
     [deps.IntervalArithmetic.extensions]
     IntervalArithmeticDiffRulesExt = "DiffRules"
@@ -638,9 +648,9 @@ weakdeps = ["Dates", "Test"]
     InverseFunctionsTestExt = "Test"
 
 [[deps.IrrationalConstants]]
-git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2"
+git-tree-sha1 = "e2222959fbc6c19554dc15174c81bf7bf3aa691c"
 uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
-version = "0.2.2"
+version = "0.2.4"
 
 [[deps.Isoband]]
 deps = ["isoband_jll"]
@@ -660,9 +670,9 @@ version = "1.0.0"
 
 [[deps.JLLWrappers]]
 deps = ["Artifacts", "Preferences"]
-git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b"
+git-tree-sha1 = "a007feb38b422fbdab534406aeca1b86823cb4d6"
 uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
-version = "1.6.1"
+version = "1.7.0"
 
 [[deps.JSON]]
 deps = ["Dates", "Mmap", "Parsers", "Unicode"]
@@ -672,9 +682,9 @@ version = "0.21.4"
 
 [[deps.JSON3]]
 deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"]
-git-tree-sha1 = "1d322381ef7b087548321d3f878cb4c9bd8f8f9b"
+git-tree-sha1 = "196b41e5a854b387d99e5ede2de3fcb4d0422aae"
 uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
-version = "1.14.1"
+version = "1.14.2"
 
     [deps.JSON3.extensions]
     JSON3ArrowExt = ["ArrowTypes"]
@@ -684,15 +694,15 @@ version = "1.14.1"
 
 [[deps.JpegTurbo]]
 deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"]
-git-tree-sha1 = "fa6d0bcff8583bac20f1ffa708c3913ca605c611"
+git-tree-sha1 = "9496de8fb52c224a2e3f9ff403947674517317d9"
 uuid = "b835a17e-a41a-41e7-81f0-2f016b05efe0"
-version = "0.1.5"
+version = "0.1.6"
 
 [[deps.JpegTurbo_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "25ee0be4d43d0269027024d75a24c24d6c6e590c"
+git-tree-sha1 = "eac1206917768cb54957c65a615460d87b455fc1"
 uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
-version = "3.0.4+0"
+version = "3.1.1+0"
 
 [[deps.KernelDensity]]
 deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"]
@@ -708,9 +718,9 @@ version = "3.100.2+0"
 
 [[deps.LERC_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "36bdbc52f13a7d1dcb0f3cd694e01677a515655b"
+git-tree-sha1 = "aaafe88dccbd957a8d82f7d05be9b69172e0cee3"
 uuid = "88015f11-f218-50d7-93a8-a6af411a945d"
-version = "4.0.0+0"
+version = "4.0.1+0"
 
 [[deps.LLVMOpenMP_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -720,9 +730,9 @@ version = "18.1.7+0"
 
 [[deps.LZO_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "854a9c268c43b77b0a27f22d7fab8d33cdb3a731"
+git-tree-sha1 = "1c602b1127f4751facb671441ca72715cc95938a"
 uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
-version = "2.10.2+1"
+version = "2.10.3+0"
 
 [[deps.LaTeXStrings]]
 git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
@@ -767,9 +777,9 @@ uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
 
 [[deps.Libffi_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290"
+git-tree-sha1 = "27ecae93dd25ee0909666e6835051dd684cc035e"
 uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
-version = "3.2.2+1"
+version = "3.2.2+2"
 
 [[deps.Libgcrypt_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"]
@@ -778,40 +788,40 @@ uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
 version = "1.11.0+0"
 
 [[deps.Libglvnd_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
-git-tree-sha1 = "6f73d1dd803986947b2c750138528a999a6c7733"
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"]
+git-tree-sha1 = "ff3b4b9d35de638936a525ecd36e86a8bb919d11"
 uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
-version = "1.6.0+0"
+version = "1.7.0+0"
 
 [[deps.Libgpg_error_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "c6ce1e19f3aec9b59186bdf06cdf3c4fc5f5f3e6"
+git-tree-sha1 = "df37206100d39f79b3376afb6b9cee4970041c61"
 uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
-version = "1.50.0+0"
+version = "1.51.1+0"
 
 [[deps.Libiconv_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "61dfdba58e585066d8bce214c5a51eaa0539f269"
+git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809"
 uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
-version = "1.17.0+1"
+version = "1.18.0+0"
 
 [[deps.Libmount_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e"
+git-tree-sha1 = "89211ea35d9df5831fca5d33552c02bd33878419"
 uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
-version = "2.40.1+0"
+version = "2.40.3+0"
 
 [[deps.Libtiff_jll]]
 deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"]
-git-tree-sha1 = "b404131d06f7886402758c9ce2214b636eb4d54a"
+git-tree-sha1 = "4ab7581296671007fc33f07a721631b8855f4b1d"
 uuid = "89763e89-9b03-5906-acba-b20f662cd828"
-version = "4.7.0+0"
+version = "4.7.1+0"
 
 [[deps.Libuuid_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807"
+git-tree-sha1 = "e888ad02ce716b319e6bdb985d2ef300e7089889"
 uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
-version = "2.40.1+0"
+version = "2.40.3+0"
 
 [[deps.LinearAlgebra]]
 deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
@@ -819,9 +829,9 @@ uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 
 [[deps.LogExpFunctions]]
 deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
-git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea"
+git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f"
 uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
-version = "0.3.28"
+version = "0.3.29"
 
     [deps.LogExpFunctions.extensions]
     LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
@@ -849,21 +859,21 @@ uuid = "23992714-dd62-5051-b70f-ba57cb901cac"
 version = "0.10.7"
 
 [[deps.MIMEs]]
-git-tree-sha1 = "1833212fd6f580c20d4291da9c1b4e8a655b128e"
+git-tree-sha1 = "c64d943587f7187e751162b3b84445bbbd79f691"
 uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
-version = "1.0.0"
+version = "1.1.0"
 
 [[deps.MKL_jll]]
 deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"]
-git-tree-sha1 = "f046ccd0c6db2832a9f639e2c669c6fe867e5f4f"
+git-tree-sha1 = "5de60bc6cb3899cd318d80d627560fae2e2d99ae"
 uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
-version = "2024.2.0+0"
+version = "2025.0.1+1"
 
 [[deps.MPICH_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
-git-tree-sha1 = "7715e65c47ba3941c502bffb7f266a41a7f54423"
+git-tree-sha1 = "3aa3210044138a1749dbd350a9ba8680869eb503"
 uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4"
-version = "4.2.3+0"
+version = "4.3.0+1"
 
 [[deps.MPIPreferences]]
 deps = ["Libdl", "Preferences"]
@@ -873,27 +883,26 @@ version = "0.1.11"
 
 [[deps.MPItrampoline_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
-git-tree-sha1 = "70e830dab5d0775183c99fc75e4c24c614ed7142"
+git-tree-sha1 = "ff91ca13c7c472cef700f301c8d752bc2aaff1a8"
 uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748"
-version = "5.5.1+0"
+version = "5.5.3+0"
 
 [[deps.MacroTools]]
-deps = ["Markdown", "Random"]
-git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df"
+git-tree-sha1 = "72aebe0b5051e5143a079a4685a46da330a40472"
 uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
-version = "0.5.13"
+version = "0.5.15"
 
 [[deps.Makie]]
-deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageBase", "ImageIO", "InteractiveUtils", "Interpolations", "IntervalSets", "InverseFunctions", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"]
-git-tree-sha1 = "5e4e0e027642293da251bf35dac408d692ccba8b"
+deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageBase", "ImageIO", "InteractiveUtils", "Interpolations", "IntervalSets", "InverseFunctions", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "PNGFiles", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"]
+git-tree-sha1 = "e64b545d25e05a609521bfc36724baa072bfd31a"
 uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
-version = "0.21.16"
+version = "0.22.2"
 
 [[deps.MakieCore]]
 deps = ["ColorTypes", "GeometryBasics", "IntervalSets", "Observables"]
-git-tree-sha1 = "ae4dbe0fcf1594ed98594e5f4ee685295a2a6f74"
+git-tree-sha1 = "605d6e8f2b7eba7f5bc6a16d297475075d5ea775"
 uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b"
-version = "0.8.10"
+version = "0.9.1"
 
 [[deps.MappedArrays]]
 git-tree-sha1 = "2dab0221fe2b0f2cb6754eaa743cc266339f527e"
@@ -929,9 +938,9 @@ version = "0.29.0"
 
 [[deps.MicrosoftMPI_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "f12a29c4400ba812841c6ace3f4efbb6dbb3ba01"
+git-tree-sha1 = "bc95bf4149bf535c09602e3acdf950d9b4376227"
 uuid = "9237b28f-5490-5468-be7b-bb81f5f5e6cf"
-version = "10.1.4+2"
+version = "10.1.4+3"
 
 [[deps.Missings]]
 deps = ["DataAPI"]
@@ -976,11 +985,11 @@ version = "1.2.0"
 
 [[deps.NyxWidgets]]
 deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
-git-tree-sha1 = "e18ab14817871c54419e4cef12f9fc4dc589f6fe"
+git-tree-sha1 = "d5addf940e4265b3ca2a6fe29b8ff24bf3b13b98"
 repo-rev = "main"
 repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
 uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
-version = "0.2.0"
+version = "0.2.1"
 
 [[deps.Observables]]
 git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
@@ -996,9 +1005,9 @@ uuid = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
 version = "0.2.4"
 
 [[deps.OffsetArrays]]
-git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e"
+git-tree-sha1 = "a414039192a155fb38c4599a60110f0018c6ec82"
 uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
-version = "1.14.1"
+version = "1.16.0"
 weakdeps = ["Adapt"]
 
     [deps.OffsetArrays.extensions]
@@ -1010,6 +1019,12 @@ git-tree-sha1 = "887579a3eb005446d514ab7aeac5d1d027658b8f"
 uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
 version = "1.3.5+1"
 
+[[deps.OpenBLASConsistentFPCSR_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "567515ca155d0020a45b05175449b499c63e7015"
+uuid = "6cdc7f73-28fd-5e50-80fb-958a8875b1af"
+version = "0.3.29+0"
+
 [[deps.OpenBLAS_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
 uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
@@ -1034,9 +1049,9 @@ version = "0.8.1+4"
 
 [[deps.OpenMPI_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML", "Zlib_jll"]
-git-tree-sha1 = "bfce6d523861a6c562721b262c0d1aaeead2647f"
+git-tree-sha1 = "da913f03f17b449951e0461da960229d4a3d1a8c"
 uuid = "fe0851c0-eecd-5654-98d4-656369965a5c"
-version = "5.0.5+0"
+version = "5.0.7+1"
 
 [[deps.OpenSSL]]
 deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
@@ -1046,15 +1061,15 @@ version = "1.4.3"
 
 [[deps.OpenSSL_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10"
+git-tree-sha1 = "a9697f1d06cc3eb3fb3ad49cc67f2cfabaac31ea"
 uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
-version = "3.0.15+1"
+version = "3.0.16+0"
 
 [[deps.OpenSpecFun_jll]]
-deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335"
 uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
-version = "0.5.5+0"
+version = "0.5.6+0"
 
 [[deps.Opus_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -1063,9 +1078,9 @@ uuid = "91d4177d-7536-5919-b921-800302f37372"
 version = "1.3.3+0"
 
 [[deps.OrderedCollections]]
-git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5"
+git-tree-sha1 = "cc4054e898b852042d7b503313f7ad03de99c3dd"
 uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
-version = "1.6.3"
+version = "1.8.0"
 
 [[deps.Oxygen]]
 deps = ["DataStructures", "Dates", "HTTP", "JSON3", "MIMEs", "Reexport", "RelocatableFolders", "Requires", "Sockets", "Statistics", "StructTypes"]
@@ -1080,21 +1095,21 @@ version = "10.42.0+1"
 
 [[deps.PDMats]]
 deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
-git-tree-sha1 = "949347156c25054de2db3b166c52ac4728cbad65"
+git-tree-sha1 = "48566789a6d5f6492688279e22445002d171cf76"
 uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
-version = "0.11.31"
+version = "0.11.33"
 
 [[deps.PNGFiles]]
 deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"]
-git-tree-sha1 = "67186a2bc9a90f9f85ff3cc8277868961fb57cbd"
+git-tree-sha1 = "cf181f0b1e6a18dfeb0ee8acc4a9d1672499626c"
 uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
-version = "0.4.3"
+version = "0.4.4"
 
 [[deps.Packing]]
 deps = ["GeometryBasics"]
-git-tree-sha1 = "ec3edfe723df33528e085e632414499f26650501"
+git-tree-sha1 = "bc5bf2ea3d5351edf285a06b0016788a121ce92c"
 uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566"
-version = "0.5.0"
+version = "0.5.1"
 
 [[deps.PaddedViews]]
 deps = ["OffsetArrays"]
@@ -1110,9 +1125,9 @@ version = "2.8.1"
 
 [[deps.Pixman_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"]
-git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b"
+git-tree-sha1 = "db76b1ecd5e9715f3d043cec13b2ec93ce015d53"
 uuid = "30392449-352a-5448-841d-b1acce4e97dc"
-version = "0.43.4+0"
+version = "0.44.2+0"
 
 [[deps.Pkg]]
 deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
@@ -1167,9 +1182,9 @@ uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
 version = "1.10.2"
 
 [[deps.PtrArrays]]
-git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f"
+git-tree-sha1 = "1d36ef11a9aaf1e8b74dacc6a731dd1de8fd493d"
 uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
-version = "1.2.1"
+version = "1.3.0"
 
 [[deps.QOI]]
 deps = ["ColorTypes", "FileIO", "FixedPointNumbers"]
@@ -1179,9 +1194,9 @@ version = "1.0.1"
 
 [[deps.QuadGK]]
 deps = ["DataStructures", "LinearAlgebra"]
-git-tree-sha1 = "cda3b045cf9ef07a08ad46731f5a3165e56cf3da"
+git-tree-sha1 = "9da16da70037ba9d701192e27befedefb91ec284"
 uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
-version = "2.11.1"
+version = "2.11.2"
 
     [deps.QuadGK.extensions]
     QuadGKEnzymeExt = "Enzyme"
@@ -1243,9 +1258,9 @@ version = "1.0.1"
 
 [[deps.Requires]]
 deps = ["UUIDs"]
-git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
+git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64"
 uuid = "ae029012-a4dd-5104-9daa-d747884805df"
-version = "1.3.0"
+version = "1.3.1"
 
 [[deps.Rmath]]
 deps = ["Random", "Rmath_jll"]
@@ -1280,9 +1295,9 @@ version = "0.7.0"
 
 [[deps.SIMD]]
 deps = ["PrecompileTools"]
-git-tree-sha1 = "52af86e35dd1b177d051b12681e1c581f53c281b"
+git-tree-sha1 = "fea870727142270bdf7624ad675901a1ee3b4c87"
 uuid = "fdea26ae-647d-5447-a871-4b548cad5224"
-version = "3.7.0"
+version = "3.7.1"
 
 [[deps.Scratch]]
 deps = ["Dates"]
@@ -1294,10 +1309,10 @@ version = "1.2.1"
 uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
 
 [[deps.ShaderAbstractions]]
-deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays", "StructArrays", "Tables"]
-git-tree-sha1 = "79123bc60c5507f035e6d1d9e563bb2971954ec8"
+deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays"]
+git-tree-sha1 = "818554664a2e01fc3784becb2eb3a82326a604b6"
 uuid = "65257c39-d410-5151-9873-9b3e5be5013e"
-version = "0.4.1"
+version = "0.5.0"
 
 [[deps.SharedArrays]]
 deps = ["Distributed", "Mmap", "Random", "Serialization"]
@@ -1348,9 +1363,9 @@ version = "1.10.0"
 
 [[deps.SpecialFunctions]]
 deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
-git-tree-sha1 = "2f5d4697f21388cbe1ff299430dd169ef97d7e14"
+git-tree-sha1 = "64cca0c26b4f31ba18f13f6c12af7c85f478cfde"
 uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
-version = "2.4.0"
+version = "2.5.0"
 weakdeps = ["ChainRulesCore"]
 
     [deps.SpecialFunctions.extensions]
@@ -1370,9 +1385,9 @@ version = "0.1.1"
 
 [[deps.StaticArrays]]
 deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"]
-git-tree-sha1 = "777657803913ffc7e8cc20f0fd04b634f871af8f"
+git-tree-sha1 = "0feb6b9031bd5c51f9072393eb5ab3efd31bf9e4"
 uuid = "90137ffa-7385-5640-81b9-e52037218182"
-version = "1.9.8"
+version = "1.9.13"
 weakdeps = ["ChainRulesCore", "Statistics"]
 
     [deps.StaticArrays.extensions]
@@ -1396,10 +1411,10 @@ uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
 version = "1.7.0"
 
 [[deps.StatsBase]]
-deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
-git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21"
+deps = ["AliasTables", "DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
+git-tree-sha1 = "29321314c920c26684834965ec2ce0dacc9cf8e5"
 uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
-version = "0.34.3"
+version = "0.34.4"
 
 [[deps.StatsFuns]]
 deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
@@ -1414,19 +1429,22 @@ weakdeps = ["ChainRulesCore", "InverseFunctions"]
 
 [[deps.StructArrays]]
 deps = ["ConstructionBase", "DataAPI", "Tables"]
-git-tree-sha1 = "f4dc295e983502292c4c3f951dbb4e985e35b3be"
+git-tree-sha1 = "5a3a31c41e15a1e042d60f2f4942adccba05d3c9"
 uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
-version = "0.6.18"
+version = "0.7.0"
 
     [deps.StructArrays.extensions]
     StructArraysAdaptExt = "Adapt"
-    StructArraysGPUArraysCoreExt = "GPUArraysCore"
+    StructArraysGPUArraysCoreExt = ["GPUArraysCore", "KernelAbstractions"]
+    StructArraysLinearAlgebraExt = "LinearAlgebra"
     StructArraysSparseArraysExt = "SparseArrays"
     StructArraysStaticArraysExt = "StaticArrays"
 
     [deps.StructArrays.weakdeps]
     Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
     GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527"
+    KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
+    LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
     SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
     StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
 
@@ -1493,9 +1511,9 @@ version = "0.1.1"
 
 [[deps.TiffImages]]
 deps = ["ColorTypes", "DataStructures", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "Mmap", "OffsetArrays", "PkgVersion", "ProgressMeter", "SIMD", "UUIDs"]
-git-tree-sha1 = "0248b1b2210285652fbc67fd6ced9bf0394bcfec"
+git-tree-sha1 = "f21231b166166bebc73b99cea236071eb047525b"
 uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
-version = "0.11.1"
+version = "0.11.3"
 
 [[deps.TranscodingStreams]]
 git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742"
@@ -1514,9 +1532,9 @@ uuid = "981d1d27-644d-49a2-9326-4793e63143c3"
 version = "0.1.0"
 
 [[deps.URIs]]
-git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
+git-tree-sha1 = "cbbebadbcc76c5ca1cc4b4f3b0614b3e603b5000"
 uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
-version = "1.5.1"
+version = "1.5.2"
 
 [[deps.UUIDs]]
 deps = ["Random", "SHA"]
@@ -1533,9 +1551,9 @@ version = "0.4.1"
 
 [[deps.Unitful]]
 deps = ["Dates", "LinearAlgebra", "Random"]
-git-tree-sha1 = "d95fe458f26209c66a187b1114df96fd70839efd"
+git-tree-sha1 = "c0667a8e676c53d390a09dc6870b3d8d6650e2bf"
 uuid = "1986cc42-f94f-5a68-af5c-568840ba703d"
-version = "1.21.0"
+version = "1.22.0"
 weakdeps = ["ConstructionBase", "InverseFunctions"]
 
     [deps.Unitful.extensions]
@@ -1544,9 +1562,9 @@ weakdeps = ["ConstructionBase", "InverseFunctions"]
 
 [[deps.WGLMakie]]
 deps = ["Bonito", "Colors", "FileIO", "FreeTypeAbstraction", "GeometryBasics", "Hyperscript", "LinearAlgebra", "Makie", "Observables", "PNGFiles", "PrecompileTools", "RelocatableFolders", "ShaderAbstractions", "StaticArrays"]
-git-tree-sha1 = "8ac9150de978e8215e7c9ab437d6f0a673748999"
+git-tree-sha1 = "94de96cef3e4b9ec09096f6e5e9814041d59300c"
 uuid = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"
-version = "0.10.16"
+version = "0.11.2"
 
 [[deps.WebP]]
 deps = ["CEnum", "ColorTypes", "FileIO", "FixedPointNumbers", "ImageCore", "libwebp_jll"]
@@ -1568,69 +1586,69 @@ version = "1.0.0"
 
 [[deps.XML2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"]
-git-tree-sha1 = "6a451c6f33a176150f315726eba8b92fbfdb9ae7"
+git-tree-sha1 = "b8b243e47228b4a3877f1dd6aee0c5d56db7fcf4"
 uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
-version = "2.13.4+0"
+version = "2.13.6+1"
 
 [[deps.XSLT_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "XML2_jll", "Zlib_jll"]
-git-tree-sha1 = "a54ee957f4c86b526460a720dbc882fa5edcbefc"
+git-tree-sha1 = "7d1671acbe47ac88e981868a078bd6b4e27c5191"
 uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
-version = "1.1.41+0"
+version = "1.1.42+0"
 
 [[deps.XZ_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "15e637a697345f6743674f1322beefbc5dcd5cfc"
+git-tree-sha1 = "56c6604ec8b2d82cc4cfe01aa03b00426aac7e1f"
 uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800"
-version = "5.6.3+0"
+version = "5.6.4+1"
 
 [[deps.Xorg_libX11_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
-git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495"
+git-tree-sha1 = "9dafcee1d24c4f024e7edc92603cedba72118283"
 uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
-version = "1.8.6+0"
+version = "1.8.6+3"
 
 [[deps.Xorg_libXau_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8"
+git-tree-sha1 = "e9216fdcd8514b7072b43653874fd688e4c6c003"
 uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
-version = "1.0.11+0"
+version = "1.0.12+0"
 
 [[deps.Xorg_libXdmcp_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7"
+git-tree-sha1 = "89799ae67c17caa5b3b5a19b8469eeee474377db"
 uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
-version = "1.1.4+0"
+version = "1.1.5+0"
 
 [[deps.Xorg_libXext_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"]
-git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85"
+git-tree-sha1 = "d7155fea91a4123ef59f42c4afb5ab3b4ca95058"
 uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
-version = "1.3.6+0"
+version = "1.3.6+3"
 
 [[deps.Xorg_libXrender_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"]
-git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe"
+git-tree-sha1 = "a490c6212a0e90d2d55111ac956f7c4fa9c277a6"
 uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
-version = "0.9.11+0"
+version = "0.9.11+1"
 
 [[deps.Xorg_libpthread_stubs_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9"
+git-tree-sha1 = "c57201109a9e4c0585b208bb408bc41d205ac4e9"
 uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
-version = "0.1.1+0"
+version = "0.1.2+0"
 
 [[deps.Xorg_libxcb_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
-git-tree-sha1 = "bcd466676fef0878338c61e655629fa7bbc69d8e"
+git-tree-sha1 = "1a74296303b6524a0472a8cb12d3d87a78eb3612"
 uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
-version = "1.17.0+0"
+version = "1.17.0+3"
 
 [[deps.Xorg_xtrans_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77"
+git-tree-sha1 = "6dba04dbfb72ae3ebe5418ba33d087ba8aa8cb00"
 uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
-version = "1.5.0+0"
+version = "1.5.1+0"
 
 [[deps.Zlib_jll]]
 deps = ["Libdl"]
@@ -1639,9 +1657,9 @@ version = "1.2.13+1"
 
 [[deps.Zstd_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b"
+git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308"
 uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
-version = "1.5.6+1"
+version = "1.5.7+1"
 
 [[deps.isoband_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
@@ -1651,15 +1669,15 @@ version = "0.2.3+0"
 
 [[deps.libaec_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "46bf7be2917b59b761247be3f317ddf75e50e997"
+git-tree-sha1 = "f5733a5a9047722470b95a81e1b172383971105c"
 uuid = "477f73a3-ac25-53e9-8cc3-50b2fa2566f0"
-version = "1.1.2+0"
+version = "1.1.3+0"
 
 [[deps.libaom_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "1827acba325fdcdf1d2647fc8d5301dd9ba43a9d"
+git-tree-sha1 = "522c1df09d05a71785765d19c9524661234738e9"
 uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b"
-version = "3.9.0+0"
+version = "3.11.0+0"
 
 [[deps.libass_jll]]
 deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
@@ -1680,15 +1698,15 @@ version = "2.0.3+0"
 
 [[deps.libpng_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
-git-tree-sha1 = "b70c870239dc3d7bc094eb2d6be9b73d27bef280"
+git-tree-sha1 = "068dfe202b0a05b8332f1e8e6b4080684b9c7700"
 uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
-version = "1.6.44+0"
+version = "1.6.47+0"
 
 [[deps.libsixel_jll]]
-deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "libpng_jll"]
-git-tree-sha1 = "7dfa0fd9c783d3d0cc43ea1af53d69ba45c447df"
+deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "libpng_jll"]
+git-tree-sha1 = "c1733e347283df07689d71d61e14be986e49e47a"
 uuid = "075b6546-f08a-558a-be8f-8157d0f608a5"
-version = "1.10.3+1"
+version = "1.10.5+0"
 
 [[deps.libvorbis_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
@@ -1698,9 +1716,9 @@ version = "1.3.7+2"
 
 [[deps.libwebp_jll]]
 deps = ["Artifacts", "Giflib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libglvnd_jll", "Libtiff_jll", "libpng_jll"]
-git-tree-sha1 = "ccbb625a89ec6195856a50aa2b668a5c08712c94"
+git-tree-sha1 = "d2408cac540942921e7bd77272c32e58c33d8a77"
 uuid = "c5f90fcd-3b7e-5836-afba-fc50a0988cb2"
-version = "1.4.0+0"
+version = "1.5.0+0"
 
 [[deps.nghttp2_jll]]
 deps = ["Artifacts", "Libdl"]
@@ -1709,9 +1727,9 @@ version = "1.52.0+1"
 
 [[deps.oneTBB_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "7d0ea0f4895ef2f5cb83645fa689e52cb55cf493"
+git-tree-sha1 = "d5a767a3bb77135a99e433afe0eb14cd7f6914c3"
 uuid = "1317d2d5-d96f-522e-a858-c73665f53c3e"
-version = "2021.12.0+0"
+version = "2022.0.0+0"
 
 [[deps.p7zip_jll]]
 deps = ["Artifacts", "Libdl"]
@@ -1720,9 +1738,9 @@ version = "17.4.0+2"
 
 [[deps.x264_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "35976a1216d6c066ea32cba2150c4fa682b276fc"
+git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7"
 uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
-version = "10164.0.0+0"
+version = "10164.0.1+0"
 
 [[deps.x265_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
diff --git a/src/REST/Client.jl b/src/REST/Client.jl
index 61814f4..1a91483 100644
--- a/src/REST/Client.jl
+++ b/src/REST/Client.jl
@@ -50,8 +50,8 @@ end
 
 function listfiles(tagger::RemoteTagger, srcdir::String)
     resp = HTTP.get("$(url(tagger, "list-files"))/$srcdir")
-    @info "listfiles" transcode(String, resp.body)
-    String[]
+    resp = transcode(String, resp.body)
+    return JSON3.read(resp)
 end
 
 function Taggers.pull(tagger::RemoteTagger, destdir::String)
@@ -85,7 +85,13 @@ end
 
 function Taggers.pushfile(tagger::RemoteTagger, src)
     request = url(tagger, "push-file")
-    @info "dummy push-file" src
+    filename = basename(src)
+    content = read(src, String)
+    # emulate curl's behavior
+    boundary = "------------------------8vna2TYHERnQGhqhQLdlHq" # whatever
+    headers = [("Content-Type" => "multipart/form-data; boundary=$boundary")]
+    body = "$boundary\r\nContent-Disposition: form-data; filename=\"$filename\"\r\nContent-Type: application/octet-steam\r\n\r\n$content\r\n$boundary--\r\n"
+    HTTP.post(request, headers, body)
 end
 
 function Taggers.push(tagger::RemoteTagger, file::String, metadata; clean=true)
diff --git a/test/rest_client.sh b/test/rest_client.sh
index 23daccb..9763454 100755
--- a/test/rest_client.sh
+++ b/test/rest_client.sh
@@ -6,6 +6,12 @@ this_script=$(realpath "${BASH_SOURCE[0]}")
 larvatagger_jl_project_root=$(dirname "$(dirname "${this_script}")")
 larvatagger_project_root=$(dirname "${larvatagger_jl_project_root}")
 
+if [ -z "$JULIA_VERSION" ]; then
+  JULIA_VERSION=1.10
+else
+  echo "Using environment variable: JULIA_VERSION= $JULIA_VERSION"
+fi
+
 tagging_backend=MaggotUBA
 tagging_model=20230311
 lt_backend_port=9285
@@ -13,20 +19,23 @@ lt_backend_port=9285
 # pre-requisites are similar to rest_server.sh
 
 # pre-compile
-julia +1.10 --project="${larvatagger_jl_project_root}" -q -e "using LarvaTagger"
+julia "+$JULIA_VERSION" --project="${larvatagger_jl_project_root}" -q -e "using LarvaTagger"
 
 # run and background the backend server
 JULIA_PROJECT="${larvatagger_project_root}/TaggingBackends" \
-  julia +1.10 --project="${larvatagger_jl_project_root}" -i \
+  julia "+$JULIA_VERSION" --project="${larvatagger_jl_project_root}" -i \
   -e "using LarvaTagger.REST.Server; run_backend(\"${larvatagger_project_root}\"; port=${lt_backend_port})" &
 lt_backend_pid=$!
 
 # run the frontend server
-JULIA="julia +1.10" ${larvatagger_jl_project_root}/scripts/larvatagger-gui.jl http://localhost:${lt_backend_port}
+JULIA="julia +$JULIA_VERSION" ${larvatagger_jl_project_root}/scripts/larvatagger-gui.jl http://localhost:${lt_backend_port}
 
 # scenarii:
 # * click on "Autotag" prior to loading any files (if not disabled, as with a bug in v0.19)
-# * load a tracking data file, and then click on "Autotag"
+# * load a tracking data file, and then click on "Autotag";
+#   expected: a predicted.label is generated and the GUI reloads
+# * load a second tracking data file (binary if first was ascii or vice versa), select another model instance, click again on "Autotag";
+#   expected: a new token was issued + similar outcome as previous step, with tracking data file and tagging model properly identified in the predicted.label file
 
 kill $lt_backend_pid
 wait $lt_backend_pid
-- 
GitLab


From a83df5d3d76c0cef7cae402a7216d2d71d60e0b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Mon, 31 Mar 2025 15:35:45 +0200
Subject: [PATCH 4/8] new install defaults

---
 Manifest-v1.11.toml |  4 +--
 Manifest.toml       |  4 +--
 scripts/install.sh  | 81 +++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 79 insertions(+), 10 deletions(-)

diff --git a/Manifest-v1.11.toml b/Manifest-v1.11.toml
index 4a3fd72..0aa18a8 100644
--- a/Manifest-v1.11.toml
+++ b/Manifest-v1.11.toml
@@ -997,11 +997,11 @@ version = "1.2.0"
 
 [[deps.NyxWidgets]]
 deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
-git-tree-sha1 = "d5addf940e4265b3ca2a6fe29b8ff24bf3b13b98"
+git-tree-sha1 = "cdc029870ac2ab9522e700f3e2b3209b33389fa4"
 repo-rev = "main"
 repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
 uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
-version = "0.2.1"
+version = "0.2.2"
 
 [[deps.Observables]]
 git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
diff --git a/Manifest.toml b/Manifest.toml
index 5b90db9..ddca19f 100644
--- a/Manifest.toml
+++ b/Manifest.toml
@@ -985,11 +985,11 @@ version = "1.2.0"
 
 [[deps.NyxWidgets]]
 deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
-git-tree-sha1 = "d5addf940e4265b3ca2a6fe29b8ff24bf3b13b98"
+git-tree-sha1 = "cdc029870ac2ab9522e700f3e2b3209b33389fa4"
 repo-rev = "main"
 repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
 uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
-version = "0.2.1"
+version = "0.2.2"
 
 [[deps.Observables]]
 git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
diff --git a/scripts/install.sh b/scripts/install.sh
index a1a2b1d..d649e46 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -1,5 +1,38 @@
 #!/usr/bin/env bash
 
+for flag in "$@"; do
+  if [ "$flag" = "-h" -o "$flag" = "--help" ]; then
+    echo "Command-line installer for LarvaTagger"
+    echo
+    echo "Usage:"
+    echo "  $0"
+    echo "  WITH_BACKEND=1 $0"
+    echo "  WITH_BACKEND=1 $0 --legacy"
+    echo "  $0 --with-default-backend"
+    echo "  $0 --with-backend --legacy"
+    echo "  $0 --uninstall"
+    echo "  $0 --help"
+    echo
+    echo "The legacy installation path for the MaggotUBA-based tagger relies"
+    echo "on Python3.8 and Torch1."
+    echo "The current default relies on Python3.11 and Torch2."
+    echo "Other environment variables can be set, similarly to WITH_BACKEND,"
+    echo "to control the script's behavior:"
+    echo "  variable                    default value"
+    echo "  BIN_DIR                     ~/.local/bin"
+    echo "  LARVATAGGER_PATH            ~/.local/share/larvatagger"
+    echo "  JULIA_CHANNEL               lts"
+    echo "  JULIA_VERSION               1.10"
+    echo "  PYTHON_VERSION              3.11  (3.8 if --legacy)"
+    echo "  MAGGOTUBA_CORE_BRANCH       main"
+    echo "  MAGGOTUBA_ADAPTER_BRANCH    torch2  (main if --legacy)"
+    echo "  TAGGINGBACKENDS_BRANCH      main"
+    echo "  PLANARLARVAE_BRANCH         main"
+    echo "  LARVATAGGER_BRANCH          main"
+    exit 0
+  fi
+done
+
 [ -d "`pwd`" ] || cd
 
 if [ -z "$BIN_DIR" ]; then
@@ -41,28 +74,47 @@ if [ "$1" = "--uninstall" ]; then
   fi
 else
 
+# former default:
 PYTHON_VERSION=3.8
+
 # the internal_<VAR> variables need to be set non-empty only if
 # the corresponding <VAR> variable is non-empty; they are used to
 # determine whether or not to report externally sourced variables
 internal_WITH_BACKEND=
 internal_MAGGOTUBA_ADAPTER_BRANCH=
 internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=
+internal_LEGACY=
+internal_WITH_DEFAULT_BACKEND=
+
+# primary use cases:
+# WITH_BACKEND=1 scripts/install.sh
+# WITH_BACKEND=1 scripts/install.sh --legacy
+# scripts/install.sh --with-default-backend
+# scripts/install.sh --with-backend
+# scripts/install.sh --with-backend --experimental
+# scripts/install.sh --with-backend --legacy
+
 for arg in "$@"; do
   if [ "$arg" = "--with-default-backend" ]; then
     WITH_BACKEND=1
     internal_WITH_BACKEND=1
-    MAGGOTUBA_CORE_BRANCH=
-    MAGGOTUBA_ADAPTER_BRANCH=
+    internal_WITH_DEFAULT_BACKEND=1
+    if [ "$internal_LEGACY" = "1" ]; then
+      echo "Ignoring --legacy; pass --with-backend --legacy instead"
+      internal_LEGACY=0
+    fi
     break
   elif [ "$arg" = "--with-backend" ]; then
     WITH_BACKEND=1
     internal_WITH_BACKEND=1
   elif [ "$arg" = "--experimental" ]; then
+    echo "The --experimental flag is deprecated and is now default"
+    internal_LEGACY=0
+  elif [ "$arg" = "--legacy" ]; then
+    internal_LEGACY=1
     MAGGOTUBA_CORE_BRANCH=
-    MAGGOTUBA_ADAPTER_BRANCH=torch2
-    internal_MAGGOTUBA_ADAPTER_BRANCH=1
-    PYTHON_VERSION=3.11
+    # former default (empty) falls back to main
+    MAGGOTUBA_ADAPTER_BRANCH=
   elif [ "$arg" = "--free-python-dependencies" ]; then
     internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=1
   elif [ "$arg" = "--lock-python-dependencies" ]; then
@@ -70,6 +122,23 @@ for arg in "$@"; do
   fi
 done
 
+if [ "$WITH_BACKEND" = "1" -a "$internal_LEGACY" = "0" ]; then
+  # new defaults (formerly "--experimental")
+  MAGGOTUBA_CORE_BRANCH=
+  MAGGOTUBA_ADAPTER_BRANCH=torch2
+  internal_MAGGOTUBA_ADAPTER_BRANCH=1
+  PYTHON_VERSION=3.11
+elif [ "$internal_LEGACY" != "1" ]; then
+  if [ "$internal_WITH_DEFAULT_BACKEND" = "1" ]; then
+    echo "The Python3.11/Torch2 backend is now default"
+    echo "Pass flags --with-backend --legacy to select the former Python3.8/Torch1 backend"
+  fi
+  MAGGOTUBA_CORE_BRANCH=
+  MAGGOTUBA_ADAPTER_BRANCH=torch2
+  internal_MAGGOTUBA_ADAPTER_BRANCH=1
+  PYTHON_VERSION=3.11
+fi
+
 PYTHON="python$PYTHON_VERSION"
 
 check_brew() {
@@ -266,7 +335,7 @@ if [ -d LarvaTagger.jl ]; then
   echo "LarvaTagger.jl installation found; skipping"
 else
   if [ -z "$LARVATAGGER_BRANCH" ]; then
-    LARVATAGGER_BRANCH=dev
+    LARVATAGGER_BRANCH=main
   else
     echo "Using environment variable: LARVATAGGER_BRANCH= $LARVATAGGER_BRANCH"
   fi
-- 
GitLab


From e77dd8a5993fee6200e6d6f1d51fe528b87fcae4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Tue, 1 Apr 2025 12:58:38 +0200
Subject: [PATCH 5/8] feat: tooltip text for tagging backends and taggers

---
 README.md                         |  9 ++---
 recipes/Dockerfile                |  5 ++-
 recipes/Dockerfile.local          | 20 ++++++----
 recipes/Dockerfile.pasteurjanelia |  3 +-
 recipes/patch.sh                  | 62 +++++++++++++++++++++++++++++++
 scripts/larvatagger.sh            | 54 +++++++++++++++++++++++----
 src/REST/Client.jl                | 17 ++++++++-
 src/REST/Model.jl                 | 51 ++++++++++++++++++-------
 src/larvatagger.js                | 13 +++++--
 src/wgl.jl                        | 14 +++++--
 test/rest_server.sh               | 32 +++++-----------
 11 files changed, 209 insertions(+), 71 deletions(-)
 create mode 100755 recipes/patch.sh

diff --git a/README.md b/README.md
index 9996377..6f7173b 100644
--- a/README.md
+++ b/README.md
@@ -111,15 +111,12 @@ curl -sSL "https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/raw/dev/scripts/instal
 ```
 
 In the latter case, the script may install several extra dependencies, but not all of them.
-In particular, Python is required; either 3.8 with `--with-default-backend`, or 3.11 with `--with-backend --experimental`.
+In particular, Python is required; either 3.11 with `--with-default-backend`, or 3.8 with `--with-backend --legacy`.
 If `pyenv` is available, the script will use this tool to install Python.
-Otherwise, `python3.8` and `python3.8-venv` may have to be manually installed.
+Otherwise, `python3.11` and `python3.11-venv` may have to be manually installed.
 On WSL, the script will attempt to install `pyenv` and Python (tested with Ubuntu 20.04).
 
-On macOS, the full LarvaTagger suite can be installed only with the `--with-backend --experimental` options:
-```
-curl -sSL "https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/raw/dev/scripts/install.sh?ref_type=heads&inline=false" | /bin/bash -s -- --with-backend --experimental
-```
+On macOS, the full LarvaTagger suite can be installed only with the default options (`--legacy` is not supported).
 
 This script can also uninstall LarvaTagger (if installed with the same script) with: `curl -sSL "https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/raw/dev/scripts/install.sh?ref_type=heads&inline=false" | /bin/bash -s -- --uninstall` which can be useful for example prior to reinstalling after failure.
 
diff --git a/recipes/Dockerfile b/recipes/Dockerfile
index 11d0e1c..b83d356 100644
--- a/recipes/Dockerfile
+++ b/recipes/Dockerfile
@@ -1,4 +1,4 @@
-FROM julia:1.10.8-bullseye AS base
+FROM julia:1.10.9-bullseye AS base
 
 ARG PROJECT_DIR=/app
 ARG BRANCH=main
@@ -82,7 +82,8 @@ RUN if [ -z $TAGGINGBACKENDS_BRANCH ]; then \
  && poetry add "pynvml==11.4.1" \
  && if [ "$(echo $BACKEND | cut -d/ -f2)" = "main" ] || [ "$(echo $BACKEND | cut -d/ -f2)" = "dev" ]; then \
     julia -e 'using Pkg; Pkg.add("JSON3")' \
- && scripts/make_models.jl default; \
+ && scripts/make_models.jl default \
+ && recipes/patch.sh; \
     fi \
  && rm -rf ~/.cache; \
     fi
diff --git a/recipes/Dockerfile.local b/recipes/Dockerfile.local
index 5d03313..34d31fc 100644
--- a/recipes/Dockerfile.local
+++ b/recipes/Dockerfile.local
@@ -1,30 +1,31 @@
 # To be built with scripts/larvatagger.sh build --dev
 
-FROM julia:1.8.2-bullseye
+FROM julia:1.10.9-bullseye
 
 ENV JULIA_PROJECT=/app/TaggingBackends
 ENV JULIA_DEPOT_PATH=/usr/local/share/julia
 ENV POETRY_VIRTUALENVS_PATH=/usr/local/share/poetry
 
 # We assume:
-# * current directory name is LarvaTagger; contains the LarvaTagger.jl project;
-# * PlanarLarvae.jl project is available as sibling directory PlanarLarvae;
+# * current directory name is LarvaTagger.jl; contains the LarvaTagger.jl project;
+# * PlanarLarvae.jl project is available as sibling directory PlanarLarvae.jl;
 # * TaggingBackends project is available as sibling directory TaggingBackends;
 # * MaggotUBA-core project is available as sibling directory MaggotUBA-core;
 # * MaggotUBA-adapter project is available as sibling directory MaggotUBA-adapter.
 # Paths are given relative to parent directory, since larvatagger.sh will move 1 level up
 # prior to calling docker build
 
-ARG PLANARLARVAE=./PlanarLarvae
-ARG LARVATAGGER=./LarvaTagger
+ARG PLANARLARVAE=./PlanarLarvae.jl
+ARG LARVATAGGER=./LarvaTagger.jl
 ARG TAGGINGBACKENDS=./TaggingBackends
 ARG MAGGOTUBA_CORE=./MaggotUBA-core
 ARG MAGGOTUBA_ADAPTER=./MaggotUBA-adapter
 
 COPY $PLANARLARVAE/src /app/PlanarLarvae/src
-COPY $PLANARLARVAE/Project.toml $PLANARLARVAE/Manifest.toml /app/PlanarLarvae/
+COPY $PLANARLARVAE/Project.toml $PLANARLARVAE/Manifest.toml* /app/PlanarLarvae/
 COPY $LARVATAGGER/src /app/src
 COPY $LARVATAGGER/scripts /app/scripts
+COPY $LARVATAGGER/recipes/patch.sh /app/recipes/
 COPY $LARVATAGGER/Project.toml $LARVATAGGER/Manifest.toml /app/
 
 RUN apt-get update \
@@ -43,7 +44,8 @@ COPY $MAGGOTUBA_CORE/src /app/MaggotUBA-core/src
 COPY $MAGGOTUBA_CORE/pyproject.toml /app/MaggotUBA-core/
 COPY $MAGGOTUBA_ADAPTER/src /app/MaggotUBA/src
 COPY $MAGGOTUBA_ADAPTER/pyproject.toml /app/MaggotUBA/
-COPY $MAGGOTUBA_ADAPTER/models/20221005 /app/MaggotUBA/models/20221005
+COPY $MAGGOTUBA_ADAPTER/models/20230311 /app/MaggotUBA/models/20230311
+COPY $MAGGOTUBA_ADAPTER/models/20230311-0 /app/MaggotUBA/models/20230311-0
 COPY $MAGGOTUBA_ADAPTER/pretrained_models/default /app/MaggotUBA/pretrained_models/default
 
 RUN python3 -m pip install poetry \
@@ -54,6 +56,8 @@ RUN python3 -m pip install poetry \
  && cd /app/MaggotUBA \
  && poetry add ../MaggotUBA-core \
  && poetry add ../TaggingBackends \
- && poetry install
+ && poetry install \
+ && cd /app \
+ && recipes/patch.sh
 
 ENTRYPOINT ["larvatagger.jl"]
diff --git a/recipes/Dockerfile.pasteurjanelia b/recipes/Dockerfile.pasteurjanelia
index 6492460..cdb79e2 100644
--- a/recipes/Dockerfile.pasteurjanelia
+++ b/recipes/Dockerfile.pasteurjanelia
@@ -27,5 +27,6 @@ RUN cd $PROJECT_DIR \
  && make package \
  && rm -rf bin/matlab/2023b/bin/glnxa64/matlab_startup_plugins/matlab_graphics_ui \
  && rm -rf bin/matlab/2023b/bin/glnxa64/matlab_startup_plugins/foundation/platform/pf_matlab_integ \
- && rm -rf .git ~/.cache
+ && rm -rf .git ~/.cache \
+ && cd .. && recipes/patch.sh
 
diff --git a/recipes/patch.sh b/recipes/patch.sh
new file mode 100755
index 0000000..b32d491
--- /dev/null
+++ b/recipes/patch.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+# patch the taggers in a Docker image so that they include metadata.json files
+
+if [ -d "MaggotUBA" ]; then
+  if ! [ -f "MaggotUBA/metadata.json" ]; then
+    cat <<"EOF" >MaggotUBA/metadata.json
+{
+  "name": "MaggotUBA",
+  "homepage": "https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter",
+  "description": "Action classifiers based on MaggotUBA encoders"
+}
+EOF
+  fi
+
+  dir="MaggotUBA/models/20230311"
+  if [ -d "$dir" ] && ! [ -f "$dir/metadata.json" ]; then
+    cat <<"EOF" >$dir/metadata.json
+{
+  "name": "20230311",
+  "homepage": "https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter#20230311-0-and-20230311",
+  "description": "Tagger trained on t15 to emulate JBM's tagger on the 7-behavior classification task"
+}
+EOF
+  fi
+
+  dir="MaggotUBA/models/20230311-0"
+  if [ -d "$dir" ] && ! [ -f "$dir/metadata.json" ]; then
+    cat <<"EOF" >$dir/metadata.json
+{
+  "name": "20230311-0",
+  "homepage": "https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter#20230311-0-and-20230311",
+  "description": "Tagger trained on t15 to emulate JBM's tagger on the 12-behavior classification task"
+}
+EOF
+  fi
+
+fi
+
+if [ -d "PasteurJanelia" ]; then
+  if ! [ -f "PasteurJanelia/metadata.json" ]; then
+    cat <<"EOF" >PasteurJanelia/metadata.json
+{
+  "name": "PasteurJanelia",
+  "homepage": "https://gitlab.pasteur.fr/nyx/PasteurJanelia-adapter",
+  "description": "Action classifiers initially designed by JBM at Janelia"
+}
+EOF
+  fi
+
+  dir="PasteurJanelia/models/5layers"
+  if [ -d "$dir" ] && ! [ -f "$dir/metadata.json" ]; then
+    cat <<"EOF" >$dir/metadata.json
+{
+  "name": "5layers",
+  "homepage": "https://gitlab.pasteur.fr/nyx/PasteurJanelia-adapter",
+  "description": "JBM's final tagger for use on the t2 and t7 trackers"
+}
+EOF
+  fi
+
+fi
diff --git a/scripts/larvatagger.sh b/scripts/larvatagger.sh
index 3cf76b9..ff32aec 100755
--- a/scripts/larvatagger.sh
+++ b/scripts/larvatagger.sh
@@ -2,7 +2,7 @@
 
 for _ in $(seq $#); do
   case $1 in
-    open|import|merge|train|predict|-V|--version|--more-help|reverse-mapping|confusion)
+    open|import|merge|train|predict|-V|--version|--more-help|reverse-mapping|confusion|backend)
       cmd=$1
       shift
       break
@@ -102,6 +102,15 @@ done
 if [ -z "$no_cache" ] && [ -z "$cache" ]; then
   DOCKER_ARGS="--no-cache $DOCKER_ARGS"
 fi
+
+if [ "$BUILD" == "--dev" ]; then
+if ! [[ "$LARVATAGGER_IMAGE" == *:* ]]; then LARVATAGGER_IMAGE="${LARVATAGGER_IMAGE}:dev"; fi
+PROJECT_ROOT=$(basename $(pwd))
+cd ..
+echo "DOCKER_BUILDKIT=1 $docker build -t \"$LARVATAGGER_IMAGE\" -f \"$PROJECT_ROOT/recipes/Dockerfile.local\" ${DOCKER_ARGS}."
+DOCKER_BUILDKIT=1 $docker build -t "$LARVATAGGER_IMAGE" -f "$PROJECT_ROOT/recipes/Dockerfile.local" ${DOCKER_ARGS}.
+else
+
 if [ -z "$target" ]; then
 DOCKER_ARGS="--target $TARGET $DOCKER_ARGS"
 fi
@@ -111,19 +120,16 @@ else
   echo "Using environment variable DOCKERFILE= $DOCKERFILE"
 fi
 DOCKER_ARGS="-f \"$DOCKERFILE\" $DOCKER_ARGS"
-if [ "$BUILD" == "--dev" ]; then
-if ! [[ "$LARVATAGGER_IMAGE" == *:* ]]; then LARVATAGGER_IMAGE="${LARVATAGGER_IMAGE}:dev"; fi
-PROJECT_ROOT=$(basename $(pwd))
-cd ..
-DOCKER_BUILDKIT=1 $docker build -t "$LARVATAGGER_IMAGE" -f "$PROJECT_ROOT/recipes/Dockerfile.local" ${DOCKER_ARGS}.
-elif [ "$BUILD" == "--stable" ]; then
+
+if [ "$BUILD" == "--stable" ]; then
 if ! [[ "$LARVATAGGER_IMAGE" == *:* ]]; then LARVATAGGER_IMAGE="${LARVATAGGER_IMAGE}:stable"; fi
 $docker build -t "$LARVATAGGER_IMAGE" ${DOCKER_ARGS}.
 else
+
 if ! [[ "$LARVATAGGER_IMAGE" == *:* ]]; then LARVATAGGER_IMAGE="${LARVATAGGER_IMAGE}:latest"; fi
 if [ -z "$LARVATAGGER_BRANCH" ]; then
   if [ -z "$LARVATAGGER_DEFAULT_BRANCH" ]; then
-    LARVATAGGER_BRANCH=dev;
+    LARVATAGGER_BRANCH=dev
   else
     echo "Deprecation notice: LARVATAGGER_DEFAULT_BRANCH has been renamed LARVATAGGER_BRANCH"
     LARVATAGGER_BRANCH=$LARVATAGGER_DEFAULT_BRANCH
@@ -141,6 +147,7 @@ DOCKER_BUILD="$docker build -t "$LARVATAGGER_IMAGE" ${DOCKER_ARGS}--build-arg BR
 echo $DOCKER_BUILD
 eval $DOCKER_BUILD
 fi
+fi
 ;;
 
 	open)
@@ -174,6 +181,34 @@ done
 DOCKER_RUN="exec $docker run $RUN_ARGS -i ${DOCKER_ARGS}\"$LARVATAGGER_IMAGE\" open \"/data/$file\" $TAGGER_ARGS $@"
 echo $DOCKER_RUN
 eval $DOCKER_RUN
+;;
+
+	backend)
+
+if [ -z "$LARVATAGGER_PORT" ]; then
+LARVATAGGER_PORT=9285
+elif [ "$LARVATAGGER_PORT" != "9285" ]; then
+echo "Using environment variable: LARVATAGGER_PORT= $LARVATAGGER_PORT"
+fi
+DOCKER_ARGS="-p $LARVATAGGER_PORT:$LARVATAGGER_PORT $DOCKER_ARGS"
+
+# undocumented feature (copied-pasted from open)
+backend=MaggotUBA
+while [ -n "$1" -a "$1" = "--external-instance" ]; do
+instance=$2; shift 2
+if ! command -v realpath &>/dev/null; then
+echo "realpath: command not found"
+echo "on macOS: brew install coreutils"
+exit 1
+fi
+RUN_ARGS="$RUN_ARGS --mount type=bind,src=\"$(realpath $instance)\",dst=/app/$backend/models/$(basename $instance)"
+done
+
+RUN_ARGS="$RUN_ARGS --entrypoint=julia"
+
+DOCKER_RUN="exec $docker run $RUN_ARGS -i ${DOCKER_ARGS}\"$LARVATAGGER_IMAGE\" $@ --project=/app -e 'using LarvaTagger.REST.Server; run_backend(\"/app\"; port=$LARVATAGGER_PORT, host=\"0.0.0.0\")'"
+echo $DOCKER_RUN
+eval $DOCKER_RUN
 ;;
 
 	import | merge)
@@ -371,6 +406,7 @@ Usage: $0 build [--stable] [--with-default-backend] [--with-backend <backend>]
        $0 confusion <datarepository>
        $0 merge <filepath> [<outputfilename>] [...]
        $0 reverse-mapping <filepath> <filename> <outputfilename>
+       $0 backend
        $0 --more-help
        $0 --version
        $0 --update
@@ -390,6 +426,8 @@ the second one with unmapped labels. It generates a third label file with demapp
 from the first file. This is useful when the first file diverges from the second one by some
 manual editions, on top of label mapping.
 
+The backend command runs a LarvaTagger's REST server that listens to port 9285 per default.
+
 See --more-help for more information about additional arguments for the other commands from
 larvatagger.jl.
 EOT
diff --git a/src/REST/Client.jl b/src/REST/Client.jl
index 1a91483..611608e 100644
--- a/src/REST/Client.jl
+++ b/src/REST/Client.jl
@@ -209,11 +209,24 @@ end
 
 listmodels(back::LTBackend) = listmodels(back, Val(false))
 
-listmodels(back::LTBackend, ::Val{false}) = collect(keys(back.taggers))
+function listmodels(back::LTBackend, ::Val{false})
+    [OrderedDict("name" => name,
+                 "description" => get(back.metadata[name], :description, ""),
+                 "homepage" => get(back.metadata[name], :homepage, ""),
+                ) for name in keys(back.taggers)]
+end
 
 function listmodels(back::LTBackend, ::Val{true})
     map(back.active_tagging_backend) do tagging_backend
-        isnothing(tagging_backend) ? String[] : collect(keys(back.taggers[tagging_backend]))
+        models = OrderedDict{String, String}[]
+        for name in keys(back.taggers[tagging_backend])
+            metadata = back.metadata[tagging_backend][:models][name]
+            push!(models, OrderedDict("name" => name,
+                                      "description" => get(metadata, :description, ""),
+                                      "homepage" => get(metadata, :homepage, ""),
+                                     ))
+        end
+        return models
     end
 end
 
diff --git a/src/REST/Model.jl b/src/REST/Model.jl
index 781ce7a..9effac9 100644
--- a/src/REST/Model.jl
+++ b/src/REST/Model.jl
@@ -187,26 +187,49 @@ function pullfile(lt_backend, backend_dir, model_instance, token, filename)
     return HTTP.Response(200, header, body)
 end
 
+function loadmodel(dir, hasinstances=false)
+    metadata = nothing
+    for filename in ("metadata", "metadata.json")
+        if isfile(joinpath(dir, filename))
+            metadata = JSON3.read(joinpath(dir, filename))
+            break
+        end
+    end
+    name = basename(dir)
+    T = if hasinstances
+        Union{AbstractString, Vector{OrderedDict{AbstractString, AbstractString}}}
+    else
+        AbstractString
+    end
+    model = OrderedDict{AbstractString, T}(
+        "name" => name,
+        "description" => "",
+        "homepage" => "",
+    )
+    if !isnothing(metadata)
+        for key in keys(model)
+            key′ = Symbol(key)
+            if haskey(metadata, key′)
+                model[key] = metadata[key′]
+            end
+        end
+    end
+    return model
+end
+
 function listtaggers(lt_backend)
     inventory = Vector{OrderedDict{String, Any}}()
     backends_dir = lt_backend.root[]
-    for tagging_backend in readdir(backends_dir)
-        tagging_backend_path = joinpath(backends_dir, tagging_backend)
+    for tagging_backend_path in readdir(backends_dir; join=true)
         Taggers.isbackend(tagging_backend_path) || continue
         models_dir = joinpath(tagging_backend_path, "models")
-        models = [OrderedDict(
-            "name" => dir,
-            "description" => "",
-            "homepage" => "",
-        ) for dir in readdir(models_dir) if isdir(joinpath(models_dir, dir))]
-        push!(inventory, OrderedDict(
-            "name" => tagging_backend,
-            "description" => "",
-            "homepage" => "",
-            "models" => models,
-        ))
+        models = loadmodel.(readdir(models_dir; join=true))
+        isempty(models) && continue
+        tagging_backend = loadmodel(tagging_backend_path, true)
+        tagging_backend["models"] = models
+        push!(inventory, tagging_backend)
     end
-    return JSON3.write(inventory)
+    return JSON3.write(unique(inventory))
 end
 
 function predict(lt_backend, backend_dir, model_instance, token)
diff --git a/src/larvatagger.js b/src/larvatagger.js
index d65fb7d..8437424 100644
--- a/src/larvatagger.js
+++ b/src/larvatagger.js
@@ -132,12 +132,17 @@ const LarvaTagger = (function () {
 			selectElement.remove(0);
 		}
 		for (let i = 0; i < selectOptions.length; i++) {
-			const option = document.createElement('option');
-			option.value = selectOptions[i];
-			option.text = selectOptions[i];
+			let option = document.createElement('option'),
+          name = selectOptions[i];
+      if (typeof name === 'object') {
+        option.title = name['description'];
+        name = name['name'];
+      }
+			option.value = name;
+			option.text = name;
 			if (jlObserver !== undefined) {
 				option.ondblclick = () => {
-					jlObserver.notify(selectOptions[i]);
+					jlObserver.notify(name);
 				};
 			}
 			selectElement.append(option);
diff --git a/src/wgl.jl b/src/wgl.jl
index 6674dc5..2579cac 100644
--- a/src/wgl.jl
+++ b/src/wgl.jl
@@ -1001,17 +1001,25 @@ function backendmenu(controller, location)
     BackendMenu(backends, models, send2backend, dom_id())
 end
 
+function lowermodel(model)
+    if model isa AbstractString
+        DOM.option(model; value=model)
+    else
+        name = model["name"]
+        DOM.option(name; value=name, title=model["description"])
+    end
+end
+
 function lowerdom(bs::BackendMenu)
     model_instance = get_model_instance(bs.backends)
     update_model_instance = js"(evt)=>{ $model_instance.notify(evt.srcElement.value); }"
-    select_dom = DOM.select(DOM.option(model; value=model) for model in bs.models[];
+    select_dom = DOM.select(lowermodel.(bs.models[]);
                             onchange=update_model_instance,
                             class=css_button)
     active_backend = get_active_backend(bs.backends)
     update_active_backend = js"(evt)=>{ $active_backend.notify(evt.srcElement.value); }"
     return DOM.div(DOM.label("Backend"),
-                   DOM.select(DOM.option(backend; value=backend)
-                              for backend in get_backend_names(bs.backends);
+                   DOM.select(lowermodel.(get_backend_names(bs.backends));
                               onchange=update_active_backend,
                               class=css_button),
                    DOM.label("Model instance"),
diff --git a/test/rest_server.sh b/test/rest_server.sh
index 6de171f..d69c186 100755
--- a/test/rest_server.sh
+++ b/test/rest_server.sh
@@ -12,7 +12,7 @@ lt_backend_port=9285
 
 # assumed directory structure:
 # the MaggotUBA-adapter, TaggingBackends, NyxArtefacts and LarvaTagger.jl projects are siblings in the ${larvatagger_project_root} directory;
-# the MaggotUBA-adapter project is available both as directory MaggotUBA-adapter and link MaggotUBA (${tagging_backend});
+# the MaggotUBA-adapter project is available both as directory MaggotUBA (${tagging_backend});
 # the MaggotUBA-adapter tagging backend contains two trained models (or model instances): 20230311 and 20230311-0;
 # the NyxArtefacts project is available as directory Artefacts;
 # MaggotUBA is installed with JULIA_PROJECT pointing at the TaggingBackends directory
@@ -113,32 +113,18 @@ else
 fi
 
 # discover the backend(s) and their models
-discovered_backends=`curl -sS http://localhost:${lt_backend_port}/list-backends`
-
-backend_metadata() {
-  local desc1=
-  local link1=
-  local desc2=
-  local link2=
-  local desc3=
-  local link3=
-  echo "{\"name\":\"$1\",\"description\":\"$desc1\",\"homepage\":\"$link1\",\"models\":[{\"name\":\"20230311\",\"description\":\"$desc2\",\"homepage\":\"$link2\"},{\"name\":\"20230311-0\",\"description\":\"$desc3\",\"homepage\":\"$link3\"}]}"
-}
-expected_backends="[`backend_metadata $tagging_backend`,`backend_metadata ${tagging_backend}-adapter`]"
+discovered_taggers=`curl -sS http://localhost:${lt_backend_port}/list-taggers`
+
+expected_taggers="[{\"name\":\"MaggotUBA\",\"description\":\"Action classifiers based on MaggotUBA encoders\",\"homepage\":\"https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter\",\"models\":[{\"name\":\"20230311\",\"description\":\"Tagger trained on t15 to emulate JBM's tagger on the 7-behavior classification task\",\"homepage\":\"https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter#20230311-0-and-20230311\"},{\"name\":\"20230311-0\",\"description\":\"Tagger trained on t15 to emulate JBM's tagger on the 12-behavior classification task\",\"homepage\":\"https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter#20230311-0-and-20230311\"}]}]"
 
-if [ "$discovered_backends" = "$expected_backends" ]; then
-  echo "list-backends: success"
+if [ "$discovered_taggers" = "$expected_taggers" ]; then
+  echo "list-taggers: success"
 else
-  echo "list-backends: failure"
+  echo "list-taggers: failure"
   echo "expected:"
-  echo "$expected_backends"
+  echo "$expected_taggers"
   echo "got:"
-  echo "$discovered_backends"
-  # further diagnose
-  model_instances=(`ls "${larvatagger_project_root}/${tagging_backend}/models"`)
-  if ! [ ${#model_instances[@]} -eq 2 -a "${model_instances[0]}" = "20230311" -a  "${model_instances[1]}" = "20230311-0" ]; then
-    echo "tagging backend is not adequately set up"
-  fi
+  echo "$discovered_taggers"
 fi
 
 cp "${larvatagger_jl_project_root}"/20140805_101522@FCF_attP2_1500062@UAS_TNT_2_0003@t5@p_8_45s1x30s0s#p_8_105s10x2s10s#n#n@100.* "${larvatagger_project_root}/${tagging_backend}/data/raw/${token}/"
-- 
GitLab


From 52c7016ec4f9404427866541f4332b52f0ac1730 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Wed, 2 Apr 2025 13:26:27 +0200
Subject: [PATCH 6/8] feat: Taggers.apply_make_dataset, make_dataset metadata
 entry, version increment

---
 Project.toml            |  2 +-
 doc/develop.md          | 38 ++++++++++++++++++++++++-
 recipes/Dockerfile      |  1 +
 recipes/patch.sh        | 11 ++++++--
 scripts/install.sh      |  4 +--
 src/REST/Client.jl      |  7 +++--
 src/REST/Model.jl       | 42 +++++-----------------------
 src/Taggers.jl          | 61 +++++++++++++++++++++++++++++++++++++++--
 src/backends.jl         |  4 +--
 test/deploy_and_test.sh |  4 +--
 10 files changed, 124 insertions(+), 50 deletions(-)

diff --git a/Project.toml b/Project.toml
index 4707dab..82ad0ca 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,7 +1,7 @@
 name = "LarvaTagger"
 uuid = "8b3b36f1-dfed-446e-8561-ea19fe966a4d"
 authors = ["François Laurent", "Institut Pasteur"]
-version = "0.19.1"
+version = "0.20"
 
 [deps]
 Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
diff --git a/doc/develop.md b/doc/develop.md
index ec368bc..6648bcc 100644
--- a/doc/develop.md
+++ b/doc/develop.md
@@ -22,4 +22,40 @@ On the *Julia* side, the lower-level functionalities are provided by the *Planar
 Similarly, the *TidyObservables.jl* project has unit tests and a GitLab workflow to run these tests on every commit.
 
 For the remaining part of the *LarvaTagger* project, high-level functional tests only are available.
-These tests are available in the *LarvaTagger.jl* project, in the test directory, file *scenarii.sh*. They depend on [*shUnit2*](https://github.com/kward/shunit2).
+These tests are available in the *LarvaTagger.jl* project, as file `test/deploy_and_test.sh`. They depend on [*shUnit2*](https://github.com/kward/shunit2).
+
+The `test/deploy_and_test.sh` script implicitly tests the `scripts/install.sh` script, and then runs the `test/scenarii.sh` script.
+The `test/scenarii.sh` script requires some test data that are downloaded by the `test/deploy_and_test.sh` script.
+If these test data cannot be fetched, please contact François Laurent so that the data are made available again (the download link periodically expires).
+
+## REST
+
+The REST API can be tested running the `test/rest_server.sh` and `test/rest_client.sh` scripts.
+
+The `test/rest_server.sh` script is fully automatic and completes with a stack trace that results from killing the backend after the last test.
+
+The `test/rest_client.sh` script does not perform any actual test.
+It launches a backend and a frontend, and the user is expected to manually operate the frontend to test the communication between the backend and the frontend.
+
+## Docker images
+
+The most complete test image can be built as follows:
+
+```
+docker=docker LARVATAGGER_IMAGE=flaur/larvatagger:latest scripts/larvatagger.sh build --dev
+docker build -t larvatagger:bigfat -f recipes/Dockerfile.pasteurjanelia .
+```
+
+Subsequent tests will run the backend using `larvatagger:bigfat` image:
+```
+docker=docker LARVATAGGER_IMAGE="larvatagger:bigfat" scripts/larvatagger.sh backend
+```
+
+The frontend is more conveniently run from the source tree:
+```
+scripts/larvatagger-gui.jl http://localhost:9285
+```
+
+The `docker=docker` environment variable is required if command `podman` is available.
+The `scripts/larvatagger.sh` script falls back on using `podman` instead of `docker`, if `podman` is available, but it is recommended to perform tests using Docker.
+In addition, at present, building the image works with Docker buildx only.
diff --git a/recipes/Dockerfile b/recipes/Dockerfile
index b83d356..c4d6989 100644
--- a/recipes/Dockerfile
+++ b/recipes/Dockerfile
@@ -83,6 +83,7 @@ RUN if [ -z $TAGGINGBACKENDS_BRANCH ]; then \
  && if [ "$(echo $BACKEND | cut -d/ -f2)" = "main" ] || [ "$(echo $BACKEND | cut -d/ -f2)" = "dev" ]; then \
     julia -e 'using Pkg; Pkg.add("JSON3")' \
  && scripts/make_models.jl default \
+ && cd $PROJECT_DIR \
  && recipes/patch.sh; \
     fi \
  && rm -rf ~/.cache; \
diff --git a/recipes/patch.sh b/recipes/patch.sh
index b32d491..42da07a 100755
--- a/recipes/patch.sh
+++ b/recipes/patch.sh
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-# patch the taggers in a Docker image so that they include metadata.json files
+# patch the taggers in a Docker image so that they include metadata.json files;
+# ultimately the taggers should manage their metadata.json files themselves.
 
 if [ -d "MaggotUBA" ]; then
   if ! [ -f "MaggotUBA/metadata.json" ]; then
@@ -37,13 +38,19 @@ EOF
 
 fi
 
+# note about make_dataset:
+#  * default is equivalent to "train, finetune"
+#  * can be "always" or a comma-separated list of processing steps
+#  * valid steps are "train", "finetune", "predict", "embed"
+
 if [ -d "PasteurJanelia" ]; then
   if ! [ -f "PasteurJanelia/metadata.json" ]; then
     cat <<"EOF" >PasteurJanelia/metadata.json
 {
   "name": "PasteurJanelia",
   "homepage": "https://gitlab.pasteur.fr/nyx/PasteurJanelia-adapter",
-  "description": "Action classifiers initially designed by JBM at Janelia"
+  "description": "Action classifiers initially designed by JBM at Janelia",
+  "make_dataset": "always"
 }
 EOF
   fi
diff --git a/scripts/install.sh b/scripts/install.sh
index d649e46..30f8023 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -180,7 +180,7 @@ else
 
 if [ -z "$JULIA_VERSION" ]; then
   JULIA_VERSION=1.10
-  JULIA_CHANNEL=lts
+  JULIA_CHANNEL=1.10
 else
   echo "Using environment variable: JULIA_VERSION= $JULIA_VERSION"
   if [ -z "$JULIA_CHANNEL" ]; then
@@ -369,7 +369,7 @@ else
 
 activate() {
   # pyenv activation is necessary on WSL
-  command -v pyenv &>/dev/null && pyenv local $PYTHON_VERSION
+  command -v pyenv &>/dev/null && [ -n "`pyenv versions | grep '  $PYTHON_VERSION'`" ] && pyenv local $PYTHON_VERSION
   poetry env use $PYTHON_VERSION
 }
 
diff --git a/src/REST/Client.jl b/src/REST/Client.jl
index 611608e..93ca3c3 100644
--- a/src/REST/Client.jl
+++ b/src/REST/Client.jl
@@ -236,9 +236,10 @@ function Taggers.predict(back::LTBackend, file::String; metadata=nothing)
     connected(tagger) || connect!(tagger)
     Taggers.push(tagger, file, metadata)
     Taggers.predict(tagger)
-    labelfile = Taggers.pull(tagger, dirname(file))
-    @assert length(labelfile) == 1
-    return labelfile[1]
+    outputfiles = Taggers.pull(tagger, dirname(file))
+    @assert !isempty(outputfiles)
+    length(outputfiles) == 1 || @warn "Multiple output files" outputfiles
+    return outputfiles[1]
 end
 
 end
diff --git a/src/REST/Model.jl b/src/REST/Model.jl
index 9effac9..d712235 100644
--- a/src/REST/Model.jl
+++ b/src/REST/Model.jl
@@ -1,6 +1,6 @@
 module Model
 
-import ..Taggers: Taggers, Tagger
+import ..Taggers: Taggers, Tagger, loadmetadata, apply_make_dataset
 import HTTP: HTTP
 import JSON3
 using OrderedCollections: OrderedDict
@@ -187,45 +187,15 @@ function pullfile(lt_backend, backend_dir, model_instance, token, filename)
     return HTTP.Response(200, header, body)
 end
 
-function loadmodel(dir, hasinstances=false)
-    metadata = nothing
-    for filename in ("metadata", "metadata.json")
-        if isfile(joinpath(dir, filename))
-            metadata = JSON3.read(joinpath(dir, filename))
-            break
-        end
-    end
-    name = basename(dir)
-    T = if hasinstances
-        Union{AbstractString, Vector{OrderedDict{AbstractString, AbstractString}}}
-    else
-        AbstractString
-    end
-    model = OrderedDict{AbstractString, T}(
-        "name" => name,
-        "description" => "",
-        "homepage" => "",
-    )
-    if !isnothing(metadata)
-        for key in keys(model)
-            key′ = Symbol(key)
-            if haskey(metadata, key′)
-                model[key] = metadata[key′]
-            end
-        end
-    end
-    return model
-end
-
 function listtaggers(lt_backend)
     inventory = Vector{OrderedDict{String, Any}}()
     backends_dir = lt_backend.root[]
     for tagging_backend_path in readdir(backends_dir; join=true)
         Taggers.isbackend(tagging_backend_path) || continue
         models_dir = joinpath(tagging_backend_path, "models")
-        models = loadmodel.(readdir(models_dir; join=true))
+        models = loadmetadata.(readdir(models_dir; join=true))
         isempty(models) && continue
-        tagging_backend = loadmodel(tagging_backend_path, true)
+        tagging_backend = loadmetadata(tagging_backend_path, false)
         tagging_backend["models"] = models
         push!(inventory, tagging_backend)
     end
@@ -234,14 +204,16 @@ end
 
 function predict(lt_backend, backend_dir, model_instance, token)
     tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+    make_dataset = apply_make_dataset(tagger, "predict")
     # blocking; should we run async and expose a token-specific status api call?
-    Taggers.predict(tagger; skip_make_dataset=true)
+    Taggers.predict(tagger; make_dataset=make_dataset)
 end
 
 function embed(lt_backend, backend_dir, model_instance, token)
     tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+    make_dataset = apply_make_dataset(tagger, "embed")
     # blocking, like predict
-    Taggers.embed(tagger; skip_make_dataset=true)
+    Taggers.embed(tagger; make_dataset=make_dataset)
 end
 
 end
diff --git a/src/Taggers.jl b/src/Taggers.jl
index e7864cc..6dad6a1 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -1,8 +1,11 @@
 module Taggers
 
 import PlanarLarvae.Formats, PlanarLarvae.Dataloaders
+using OrderedCollections: OrderedDict
+using JSON3
 
-export Tagger, isbackend, resetmodel, resetdata, train, predict, finetune, embed
+export Tagger, isbackend, resetmodel, resetdata, train, predict, finetune, embed,
+       loadmetadata, apply_make_dataset
 
 struct Tagger
     backend_dir::String
@@ -300,7 +303,9 @@ function run(tagger, switch, kwargs)
     args = Any[]
     parsekwargs!(args, kwargs)
     cmd = tagging_backend_command(tagger)
-    Base.run(Cmd(`$cmd $switch $args`; dir=tagger.backend_dir))
+    cmd = Cmd(`$cmd $switch $args`; dir=tagger.backend_dir)
+    @info "Running command" cmd
+    Base.run(cmd)
 end
 
 function train(tagger::Tagger; pretrained_instance=None, kwargs...)
@@ -323,4 +328,56 @@ end
 
 embed(tagger::Tagger; kwargs...) = run(tagger, "embed", kwargs)
 
+function loadmetadata(dir, instance=true)
+    metadata = nothing
+    for filename in ("metadata", "metadata.json")
+        if isfile(joinpath(dir, filename))
+            metadata = JSON3.read(joinpath(dir, filename))
+            break
+        end
+    end
+    name = basename(dir)
+    T = if instance
+        AbstractString
+    else
+        Union{AbstractString, Vector{OrderedDict{AbstractString, AbstractString}}}
+    end
+    model = OrderedDict{AbstractString, T}(
+        "name" => name,
+        "description" => "",
+        "homepage" => "",
+        "make_dataset" => "",
+    )
+    if !isnothing(metadata)
+        for key in keys(model)
+            key′ = Symbol(key)
+            if haskey(metadata, key′)
+                model[key] = metadata[key′]
+            end
+        end
+    end
+    return model
+end
+
+function loadmetadata(tagger::Tagger, instance=true)
+    if instance
+        loadmetadata(Taggers.modeldir(tagger.backend_dir))
+    else
+        loadmetadata(tagger.backend_dir, true)
+    end
+end
+
+function apply_make_dataset(tagger::Tagger, step)
+    # see recipes/patch.sh for a note about the make_dataset entry
+    @assert step in ("train", "finetune", "predict", "embed")
+    metadata = loadmetadata(tagger, false)
+    make_dataset = metadata["make_dataset"]
+    apply_make_dataset = step in ("train", "finetune")
+    if !isempty(make_dataset)
+        apply_make_dataset = make_dataset == "always" || occursin(step, make_dataset)
+        @debug "apply_make_dataset" metadata apply_make_dataset
+    end
+    return apply_make_dataset
+end
+
 end # module
diff --git a/src/backends.jl b/src/backends.jl
index d734ad1..a3ad720 100644
--- a/src/backends.jl
+++ b/src/backends.jl
@@ -98,8 +98,8 @@ function Taggers.predict(model::Backends, file::String; metadata=nothing)
     tagger = Tagger(backend_dir, model_instance)
     #
     Taggers.push(model, file; metadata=metadata)
-    # TODO: make the skip_make_dataset option discoverable in the backend
-    predict(tagger; skip_make_dataset=true)
+    make_dataset = apply_make_dataset(tagger, "predict")
+    predict(tagger; make_dataset=make_dataset)
     labelfile = Taggers.pull(model, dirname(file))
     @assert length(labelfile) == 1
     return labelfile[1]
diff --git a/test/deploy_and_test.sh b/test/deploy_and_test.sh
index 942ffd2..b0c0113 100755
--- a/test/deploy_and_test.sh
+++ b/test/deploy_and_test.sh
@@ -23,7 +23,7 @@ if ! [ -f scripts/install.sh ]; then
 fi
 
 scripts/install.sh --uninstall
-scripts/install.sh --with-backend --experimental
+scripts/install.sh --with-default-backend
 
 #############
 ## Maestro ##
@@ -74,7 +74,7 @@ if [ -f LarvaTagger_test_data.tgz ]; then
 else
   # Not recommended; reproducibility is not guarantee across hosts or architectures yet
   (cd "$LTROOT" && \
-    wget -O- https://dl.pasteur.fr/fop/ppk8GBQf/241127_LarvaTagger_test_data.tgz | tar zxv)
+    wget -O- https://dl.pasteur.fr/fop/VVIMasjG/241127_LarvaTagger_test_data.tgz | tar zxv)
 fi
 
 if [ "$LOCAL_SCENARII" = "1" ]; then
-- 
GitLab


From 51ff20eb2ded73e9713b5e384f0a80d3e1133b93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Wed, 2 Apr 2025 16:15:32 +0200
Subject: [PATCH 7/8] fix: release-related hotfixes

---
 README.md                   | 12 ++++--------
 doc/develop.md              |  2 ++
 recipes/README.md           |  6 +-----
 recipes/release.sh          | 26 ++++++++++++++++++++++++++
 test/deploy_and_test.sh     |  3 ++-
 test/predict_and_retrain.sh |  2 ++
 6 files changed, 37 insertions(+), 14 deletions(-)
 create mode 100755 recipes/release.sh

diff --git a/README.md b/README.md
index 6f7173b..da5807a 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@ The *LarvaTagger* project is divided into several components. Although this READ
 
 This package features a web-based graphical user interface (GUI) for visualizing the tracked larvae and assigning discrete behavior tags, at each time step.
 
+As a web GUI, a public instance can be found at [nyx.pasteur.cloud](https://nyx.pasteur.cloud/larvatagger). Demo data can be found in the [Quick start](#quick-start-with-docker) section below.
+
 A command-line interface (CLI) is also available for batch processing, including the automatic tagging of track data files, training new taggers from labeled data, etc.
 
 Although *LarvaTagger.jl* alone comes with no automatic tagger, it is designed to work primarily in combination with [*MaggotUBA*](https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter) for the identification of larval actions or postures.
@@ -53,7 +55,7 @@ Change directory (`cd`) to the Downloads directory. In this example, we will ass
 
 On macOS and Linux, change the permissions of the script file so that it can be executed:
 ```
-chmod a+x larvatagger.sh
+chmod +x larvatagger.sh
 ```
 
 The demo data can be opened in the web browser for visual inspection, on macOS and Linux with:
@@ -132,12 +134,6 @@ cd LarvaTagger
 julia --project=. -e 'using Pkg; Pkg.instantiate()'
 ```
 
-In May 2024, the gitlab.pasteur.fr server began to request an authentication token on cloning public repositories.
-If the `git clone` command requests an authentication token you do not have, do instead:
-```
-curl -sSL https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/archive/main/larvatagger.jl-main.tar.gz | tar zxv && mv larvatagger.jl-main LarvaTagger
-```
-
 Calling `Pkg.instantiate` in a copy of the project is preferred over using `Pkg.add`,
 because *LarvaTagger.jl* depends on several unregistered packages.
 
@@ -218,7 +214,7 @@ Feel free to adjust the value if the 2D view is too small or large.
 
 ## Automatic tagging
 
-*LarvaTagger.jl* comes with no automatic tagger per default, unless run using Docker or installed with the *scripts/install.sh* script and the `--with-default-backend` option.
+*LarvaTagger.jl* comes with no automatic tagger per default, unless you run the Docker image or you installed LarvaTagger with the *scripts/install.sh* script and the `--with-default-backend` option.
 
 To extend the editor with automatic tagging capabilities, see the [recommended installation steps for *TaggingBackends* and *MaggotUBA*](https://gitlab.pasteur.fr/nyx/TaggingBackends#recommended-installation).
 
diff --git a/doc/develop.md b/doc/develop.md
index 6648bcc..322d13f 100644
--- a/doc/develop.md
+++ b/doc/develop.md
@@ -59,3 +59,5 @@ scripts/larvatagger-gui.jl http://localhost:9285
 The `docker=docker` environment variable is required if command `podman` is available.
 The `scripts/larvatagger.sh` script falls back on using `podman` instead of `docker`, if `podman` is available, but it is recommended to perform tests using Docker.
 In addition, at present, building the image works with Docker buildx only.
+
+See also the `recipes/release.sh` script.
diff --git a/recipes/README.md b/recipes/README.md
index 5ad9e8e..f0302b0 100644
--- a/recipes/README.md
+++ b/recipes/README.md
@@ -149,10 +149,6 @@ Optionally, you can also get the default backend (currently *20230311*) with:
 ```
 scripts/larvatagger.sh build --with-default-backend
 ```
-Currently, Docker images on Docker Hub are built with:
-```
-scripts/larvatagger.sh --target confusion build --with-default-backend
-```
 
 If you want another tagger, *e.g.* the *20230129* tagger implemented by the *20230129* branch of the *MaggotUBA-adapter* repository, do:
 ```
@@ -177,7 +173,7 @@ docker pull flaur/larvatagger
 ```
 
 Beware that images that ship with a tagging backend are relatively large files (>5GB on disk).
-If you are not interested in automatic tagging, use the `flaur/larvatagger:0.19-standalone` image instead.
+If you are not interested in automatic tagging, use the `flaur/larvatagger:0.20-standalone` image instead.
 
 ### Upgrading
 
diff --git a/recipes/release.sh b/recipes/release.sh
new file mode 100755
index 0000000..396d858
--- /dev/null
+++ b/recipes/release.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+set -e
+
+RELEASE=$1
+
+if [ -z "$RELEASE" ]; then
+  echo "Usage: $0 <version-number>"
+  exit 1
+fi
+
+docker=docker LARVATAGGER_IMAGE=flaur/larvatagger:$RELEASE-standalone scripts/larvatagger.sh build
+docker=docker LARVATAGGER_IMAGE=flaur/larvatagger:$RELEASE-20230311 scripts/larvatagger.sh --target confusion build --with-default-backend
+docker tag flaur/larvatagger:$RELEASE-20230311 flaur/larvatagger:latest
+docker build -t flaur/larvatagger:$RELEASE-bigfat -f recipes/Dockerfile.pasteurjanelia --no-cache .
+
+test/predict_and_retrain.sh
+
+cat <<EOF
+Next steps are:
+docker login
+docker push flaur/larvatagger:$RELEASE-standalone
+docker push flaur/larvatagger:$RELEASE-20230311
+docker push flaur/larvatagger:$RELEASE-bigfat
+docker push flaur/larvatagger:latest
+EOF
diff --git a/test/deploy_and_test.sh b/test/deploy_and_test.sh
index b0c0113..0de5508 100755
--- a/test/deploy_and_test.sh
+++ b/test/deploy_and_test.sh
@@ -23,7 +23,8 @@ if ! [ -f scripts/install.sh ]; then
 fi
 
 scripts/install.sh --uninstall
-scripts/install.sh --with-default-backend
+LARVATAGGER_BRANCH=dev \
+  scripts/install.sh --with-default-backend
 
 #############
 ## Maestro ##
diff --git a/test/predict_and_retrain.sh b/test/predict_and_retrain.sh
index 771145d..fd68d62 100755
--- a/test/predict_and_retrain.sh
+++ b/test/predict_and_retrain.sh
@@ -3,6 +3,8 @@
 # this script performs a few tests with the Docker image
 # TODO: implement more use cases as in scenarii.sh
 
+set -e
+
 trap 'rm -rf "$TMPDIR"' EXIT
 
 TMPDIR=$(mktemp -d) || exit 1
-- 
GitLab


From bb856490273b414d2819b108621a21d72e60e411 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Wed, 2 Apr 2025 17:16:45 +0200
Subject: [PATCH 8/8] fix: additional controls enabled by default with
 --server-url

---
 src/cli_open.jl | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/cli_open.jl b/src/cli_open.jl
index 73210d8..cdb08e2 100644
--- a/src/cli_open.jl
+++ b/src/cli_open.jl
@@ -73,6 +73,8 @@ function main(args=ARGS; exit_on_error=false)
     #     kwargs[:viewfactor] = 2
     end
 
+    proxy_url = parsed_args["--server-url"]
+
     if parsed_args["--viewer"]
         app = larvaviewer(infile; kwargs...)
     else
@@ -81,12 +83,16 @@ function main(args=ARGS; exit_on_error=false)
             kwargs[:backend_directory] = backends
         end
         kwargs[:manualtag] = string(parsed_args["--manual-label"])
+        if !isnothing(proxy_url)
+            kwargs[:enable_uploads ] = true
+            kwargs[:enable_new_directories] = true
+            kwargs[:enable_delete] = true
+        end
         app = larvaeditor(infile; kwargs...)
     end
     #
     port = parsed_args["--port"]
     port = isnothing(port) ? 9284 : parse(Int, port)
-    proxy_url = parsed_args["--server-url"]
     if isnothing(proxy_url)
         proxy_url = ""
     elseif !startswith(proxy_url, "http")
-- 
GitLab