From 51775a19bf394e43e01dc6f5b71ac85d0f9239ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Fri, 14 Jun 2024 13:28:49 +0200
Subject: [PATCH 01/18] fix: attempt to suppress Formatting.jl warning message

---
 src/LarvaTagger.jl | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/LarvaTagger.jl b/src/LarvaTagger.jl
index 337a0e3..b3484f9 100644
--- a/src/LarvaTagger.jl
+++ b/src/LarvaTagger.jl
@@ -4,16 +4,18 @@ using PlanarLarvae, PlanarLarvae.Datasets, PlanarLarvae.Formats
 using ObservationPolicies
 using TidyObservables
 
+using Logging
+Logging.disable_logging(Logging.Warn) # prior to loading old libraries
 using JSServe, WGLMakie
 using JSServe: evaljs, onjs
 using Makie
+Logging.disable_logging(Logging.Debug) # restore default
 using Format
 using Colors
 using StaticArrays
 using Statistics
 using Observables
 using Meshes
-using Logging
 import Dates
 using OrderedCollections
 using Random
-- 
GitLab


From 441aa8e6788246f9aaf328e460f3f6d9130c778c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Fri, 14 Jun 2024 15:05:29 +0200
Subject: [PATCH 02/18] fix: -Jsysimage caused arguments to be shifted

---
 scripts/larvatagger    | 13 +++++++------
 scripts/larvatagger.sh |  4 +++-
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/scripts/larvatagger b/scripts/larvatagger
index 91c4d7f..044eef8 100755
--- a/scripts/larvatagger
+++ b/scripts/larvatagger
@@ -9,17 +9,18 @@
 currentdir=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
 
 if [ "${1:0:2}" = "-J" ]; then
-cmd=$2
+args="$1 "
+shift
 else
-cmd=$1
+args=
 fi
+cmd=$1
 
 case $cmd in
 open)
   shift
   datapath=$1
   shift
-  args=
   if [ -n "$BACKENDS_PATH" ]; then
     args="--backends=\"$BACKENDS_PATH\" $args"
   fi
@@ -35,14 +36,14 @@ train|predict)
     shift
     backend=$1
     shift
-    "$currentdir/larvatagger-toolkit.jl" $cmd "$BACKENDS_PATH/$backend" $@
+    "$currentdir/larvatagger-toolkit.jl" $cmd "$BACKENDS_PATH/$backend" $args$@
   else
-    "$currentdir/larvatagger-toolkit.jl" $@
+    "$currentdir/larvatagger-toolkit.jl" $args$@
   fi
   ;;
 
 import|merge|--version|-V)
-  "$currentdir/larvatagger-toolkit.jl" $@
+  "$currentdir/larvatagger-toolkit.jl" $args$@
   ;;
 
 *)
diff --git a/scripts/larvatagger.sh b/scripts/larvatagger.sh
index 3222537..a261c70 100755
--- a/scripts/larvatagger.sh
+++ b/scripts/larvatagger.sh
@@ -158,7 +158,9 @@ fi
 RUN_ARGS="$RUN_ARGS --mount type=bind,src=\"$(realpath $instance)\",dst=/app/$backend/models/$(basename $instance)"
 done
 
-eval "exec $docker run $RUN_ARGS -i ${DOCKER_ARGS}\"$LARVATAGGER_IMAGE\" open \"/data/$file\" $TAGGER_ARGS $@"
+DOCKER_RUN="exec $docker run $RUN_ARGS -i ${DOCKER_ARGS}\"$LARVATAGGER_IMAGE\" open \"/data/$file\" $TAGGER_ARGS $@"
+echo $DOCKER_RUN
+eval $DOCKER_RUN
 ;;
 
 	import | merge)
-- 
GitLab


From 84cf062a69f6c403b51f050673fc343ec52e9cf4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Fri, 14 Jun 2024 15:21:19 +0200
Subject: [PATCH 03/18] fix: -Jsysimage caused arguments to be shifted (2)

---
 scripts/larvatagger | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/scripts/larvatagger b/scripts/larvatagger
index 044eef8..db34a6c 100755
--- a/scripts/larvatagger
+++ b/scripts/larvatagger
@@ -9,10 +9,10 @@
 currentdir=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
 
 if [ "${1:0:2}" = "-J" ]; then
-args="$1 "
+jlargs="$1 "
 shift
 else
-args=
+jlargs=
 fi
 cmd=$1
 
@@ -21,13 +21,14 @@ open)
   shift
   datapath=$1
   shift
+  ltargs=
   if [ -n "$BACKENDS_PATH" ]; then
-    args="--backends=\"$BACKENDS_PATH\" $args"
+    ltargs="--backends=\"$BACKENDS_PATH\" $args"
   fi
   if [ -n "$OPEN_BROWSER" ]; then
-    args="--browser $args"
+    ltargs="--browser $args"
   fi
-  eval "\"$currentdir/larvatagger-gui.jl\" \"$datapath\" $args$@"
+  eval "\"$currentdir/larvatagger-gui.jl\" $jlargs\"$datapath\" $ltargs$@"
   ;;
 
 train|predict)
@@ -36,14 +37,14 @@ train|predict)
     shift
     backend=$1
     shift
-    "$currentdir/larvatagger-toolkit.jl" $cmd "$BACKENDS_PATH/$backend" $args$@
+    "$currentdir/larvatagger-toolkit.jl" $jlargs$cmd "$BACKENDS_PATH/$backend" $@
   else
-    "$currentdir/larvatagger-toolkit.jl" $args$@
+    "$currentdir/larvatagger-toolkit.jl" $jlargs$@
   fi
   ;;
 
 import|merge|--version|-V)
-  "$currentdir/larvatagger-toolkit.jl" $args$@
+  "$currentdir/larvatagger-toolkit.jl" $jlargs$@
   ;;
 
 *)
-- 
GitLab


From 509354bdf428cfa0934136e7cb679e9684f747b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Fri, 14 Jun 2024 15:42:30 +0200
Subject: [PATCH 04/18] version increment

---
 Project.toml       | 2 +-
 recipes/Dockerfile | 9 +++++----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/Project.toml b/Project.toml
index 9d6f12c..7578ac2 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.18.3"
+version = "0.18.4"
 
 [deps]
 Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
diff --git a/recipes/Dockerfile b/recipes/Dockerfile
index f52fe0e..d32d5f0 100644
--- a/recipes/Dockerfile
+++ b/recipes/Dockerfile
@@ -15,11 +15,12 @@ RUN apt-get update \
  && ln -s "$JULIA_PROJECT/scripts/larvatagger" /bin \
  && mkdir -p "$JULIA_DEPOT_PATH/logs" && rm -f "$JULIA_DEPOT_PATH/logs/manifest_usage.toml" && ln -s /dev/null "$JULIA_DEPOT_PATH/logs/manifest_usage.toml"
 
-RUN $JULIA_PROJECT/test/precompile.sh --shallow \
- && mv larvatagger.so /lib/ \
- && rm -rf $JULIA_PROJECT/test/data
+# RUN $JULIA_PROJECT/test/precompile.sh --shallow \
+#  && mv larvatagger.so /lib/ \
+#  && rm -rf $JULIA_PROJECT/test/data
 
-ENTRYPOINT ["/app/scripts/larvatagger", "-J/lib/larvatagger.so"]
+ENTRYPOINT ["/app/scripts/larvatagger"]
+#, "-J/lib/larvatagger.so"]
 
 
 FROM base AS prebuild
-- 
GitLab


From e9b5b0f800f4a8bc3c3938d1e16ef2268174e9d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Thu, 25 Jul 2024 16:13:31 +0200
Subject: [PATCH 05/18] feat: temptative Dockerfile for PasteurJanelia backend

---
 Project.toml                      |  2 +-
 recipes/Dockerfile                |  2 +-
 recipes/Dockerfile.pasteurjanelia | 29 +++++++++++++++++++++++++++++
 3 files changed, 31 insertions(+), 2 deletions(-)
 create mode 100644 recipes/Dockerfile.pasteurjanelia

diff --git a/Project.toml b/Project.toml
index 7578ac2..115c84b 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.18.4"
+version = "0.18.5"
 
 [deps]
 Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
diff --git a/recipes/Dockerfile b/recipes/Dockerfile
index d32d5f0..6458645 100644
--- a/recipes/Dockerfile
+++ b/recipes/Dockerfile
@@ -28,7 +28,7 @@ FROM base AS prebuild
 ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
     PIP_NO_CACHE_DIR=1 \
     POETRY_VIRTUALENVS_IN_PROJECT=1 \
-    POETRY_VERSION=1.2.0
+    POETRY_VERSION=1.8.3
 
 RUN apt-get update \
  && apt-get install --no-install-recommends -y python3-dev python3-pip \
diff --git a/recipes/Dockerfile.pasteurjanelia b/recipes/Dockerfile.pasteurjanelia
new file mode 100644
index 0000000..43f0edb
--- /dev/null
+++ b/recipes/Dockerfile.pasteurjanelia
@@ -0,0 +1,29 @@
+ARG BASE=latest
+
+FROM flaur/larvatagger:${BASE} AS prebuild
+
+ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
+    PIP_NO_CACHE_DIR=1 \
+    POETRY_VIRTUALENVS_IN_PROJECT=1 \
+    POETRY_VERSION=1.8.3
+
+RUN apt-get update \
+ && apt-get install --no-install-recommends -y wget \
+ && if ! command -v poetry; then \
+    apt-get install --no-install-recommends -y python3-dev python3-pip \
+ && pip install "poetry==$POETRY_VERSION"; \
+    fi \
+ && rm -rf /var/lib/apt/lists/*
+
+
+FROM prebuild AS backend
+
+ARG PROJECT_DIR=/app
+ARG BACKEND_BRANCH=main
+
+RUN cd $PROJECT_DIR \
+ && git clone --depth 1 --no-tags --single-branch -b $BACKEND_BRANCH https://gitlab.pasteur.fr/nyx/PasteurJanelia-adapter PasteurJanelia \
+ && cd PasteurJanelia \
+ && make package \
+ && rm -rf .git ~/.cache
+
-- 
GitLab


From 53f78614a18ea15d08bc0cb95ed218ab6ee4f292 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Thu, 25 Jul 2024 17:54:16 +0200
Subject: [PATCH 06/18] fix: first working Docker image

---
 recipes/Dockerfile.pasteurjanelia | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/recipes/Dockerfile.pasteurjanelia b/recipes/Dockerfile.pasteurjanelia
index 43f0edb..c346374 100644
--- a/recipes/Dockerfile.pasteurjanelia
+++ b/recipes/Dockerfile.pasteurjanelia
@@ -8,7 +8,7 @@ ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
     POETRY_VERSION=1.8.3
 
 RUN apt-get update \
- && apt-get install --no-install-recommends -y wget \
+ && apt-get install --no-install-recommends -y make wget unzip \
  && if ! command -v poetry; then \
     apt-get install --no-install-recommends -y python3-dev python3-pip \
  && pip install "poetry==$POETRY_VERSION"; \
@@ -25,5 +25,7 @@ RUN cd $PROJECT_DIR \
  && git clone --depth 1 --no-tags --single-branch -b $BACKEND_BRANCH https://gitlab.pasteur.fr/nyx/PasteurJanelia-adapter PasteurJanelia \
  && cd PasteurJanelia \
  && 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
 
-- 
GitLab


From 9ae6a5b945b0cc09588031ab66b990568d35b0d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Fri, 26 Jul 2024 20:08:17 +0200
Subject: [PATCH 07/18] feat: verbose logging for externally sourced variables

---
 recipes/Dockerfile.pasteurjanelia |  2 +-
 scripts/install.sh                | 83 +++++++++++++++++++++----------
 scripts/larvatagger.sh            | 25 +++++++---
 3 files changed, 76 insertions(+), 34 deletions(-)

diff --git a/recipes/Dockerfile.pasteurjanelia b/recipes/Dockerfile.pasteurjanelia
index c346374..6492460 100644
--- a/recipes/Dockerfile.pasteurjanelia
+++ b/recipes/Dockerfile.pasteurjanelia
@@ -9,7 +9,7 @@ ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
 
 RUN apt-get update \
  && apt-get install --no-install-recommends -y make wget unzip \
- && if ! command -v poetry; then \
+ && if ! command -v poetry &>/dev/null; then \
     apt-get install --no-install-recommends -y python3-dev python3-pip \
  && pip install "poetry==$POETRY_VERSION"; \
     fi \
diff --git a/scripts/install.sh b/scripts/install.sh
index af1f164..fead1e9 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -4,9 +4,13 @@
 
 if [ -z "$BIN_DIR" ]; then
   BIN_DIR=~/.local/bin
+else
+  echo "Using environment variable: BIN_DIR= $BIN_DIR"
 fi
 if [ -z "$LARVATAGGER_PATH" ]; then
   LARVATAGGER_PATH=~/.local/share/larvatagger
+else
+  echo "Using environment variable: LARVATAGGER_PATH= $LARVATAGGER_PATH"
 fi
 
 if [ "$1" = "--uninstall" ]; then
@@ -21,52 +25,62 @@ if [ "$1" = "--uninstall" ]; then
 else
 
 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=
 for arg in "$@"; do
   if [ "$arg" = "--with-default-backend" ]; then
     WITH_BACKEND=1
+    internal_WITH_BACKEND=1
     MAGGOTUBA_CORE_BRANCH=
     MAGGOTUBA_ADAPTER_BRANCH=
     break
   elif [ "$arg" = "--with-backend" ]; then
     WITH_BACKEND=1
+    internal_WITH_BACKEND=1
   elif [ "$arg" = "--experimental" ]; then
     MAGGOTUBA_CORE_BRANCH=
     MAGGOTUBA_ADAPTER_BRANCH=torch2
+    internal_MAGGOTUBA_ADAPTER_BRANCH=1
     PYTHON_VERSION=3.11
   fi
 done
 
-if ! command -v curl >/dev/null; then
-  if command -v brew >/dev/null; then
+if ! command -v curl &>/dev/null; then
+  if command -v brew &>/dev/null; then
     # macOS users are not given the choice as they usually do not care about freedom
     brew install curl
   fi
 fi
-if ! command -v curl >/dev/null; then
+if ! command -v curl &>/dev/null; then
   echo "Command curl required; aborting"
 else
 
 check_brew() {
   if [ "`uname`" = "Darwin" ]; then
     # macOS
-    if ! command -v brew >/dev/null; then
+    if ! command -v brew &>/dev/null; then
       echo "Installing Homebrew; admin rights will be requested"
       /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
     fi
   fi
 }
 
-if ! command -v realpath >/dev/null; then
+if ! command -v realpath &>/dev/null; then
   # macOS
   check_brew
-  if command -v brew >/dev/null; then
+  if command -v brew &>/dev/null; then
     brew install coreutils
   fi
 fi
 
-if ! command -v julia >/dev/null; then
+if ! command -v julia &>/dev/null; then
   if [ -z "$JULIA_INSTALL_ARGS" ]; then
     JULIA_INSTALL_ARGS=-y
+  else
+    echo "Using environment variable: JULIA_INSTALL_ARGS= $JULIA_INSTALL_ARGS"
   fi
   curl -fsSL https://install.julialang.org | sh -s -- $JULIA_INSTALL_ARGS
   if [ -f ~/.bashrc ]; then
@@ -80,8 +94,8 @@ if [ -n "$WITH_BACKEND" ]; then
   if [ "`uname`" = "Darwin" ]; then
     echo "WARNING: the default tagging backend is not supported by macOS"
   fi
-  if ! command -v python$PYTHON_VERSION >/dev/null; then
-    if command -v pyenv >/dev/null; then
+  if ! command -v python$PYTHON_VERSION &>/dev/null; then
+    if command -v pyenv &>/dev/null; then
       [ `pyenv versions | grep $PYTHON_VERSION` ] || pyenv install $PYTHON_VERSION
     elif [[ "`uname -r`" =~ "-microsoft-standard-WSL2" ]]; then
       echo
@@ -96,21 +110,21 @@ if [ -n "$WITH_BACKEND" ]; then
       eval "$(~/.pyenv/bin/pyenv init -)"
       cat <<"EOF" >>~/.bashrc
 
-command -v pyenv >/dev/null || export PATH=~/.pyenv/bin:$PATH
+command -v pyenv &>/dev/null || export PATH=~/.pyenv/bin:$PATH
 eval "$(~/.pyenv/bin/pyenv init -)"
 EOF
       pyenv install $PYTHON_VERSION
     else
       check_brew
-      if command -v brew >/dev/null; then
+      if command -v brew &>/dev/null; then
         brew install python@$PYTHON_VERSION
       else
         echo "WARNING: command python$PYTHON_VERSION not found"
       fi
     fi
   fi
-  if ! command -v poetry >/dev/null; then
-    if command -v pipx >/dev/null; then
+  if ! command -v poetry &>/dev/null; then
+    if command -v pipx &>/dev/null; then
       pipx install poetry
     else
       curl -fsSL https://install.python-poetry.org | python3 -
@@ -127,6 +141,7 @@ else
   if [ -z "$PLANARLARVAE_BRANCH" ]; then
     PLANARLARVAE_BRANCH=main
   else
+    echo "Using environment variable: PLANARLARVAE_BRANCH= $PLANARLARVAE_BRANCH"
     curl -fsSL https://gitlab.pasteur.fr/nyx/planarlarvae.jl/-/archive/${PLANARLARVAE_BRANCH}/planarlarvae.jl-${PLANARLARVAE_BRANCH}.tar.gz | tar zxv
     mv planarlarvae.jl-${PLANARLARVAE_BRANCH} PlanarLarvae
   fi
@@ -137,6 +152,8 @@ if [ -d LarvaTagger.jl ]; then
 else
   if [ -z "$LARVATAGGER_BRANCH" ]; then
     LARVATAGGER_BRANCH=dev
+  else
+    echo "Using environment variable: LARVATAGGER_BRANCH= $LARVATAGGER_BRANCH"
   fi
   curl -fsSL https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/archive/${LARVATAGGER_BRANCH}/larvatagger.jl-${LARVATAGGER_BRANCH}.tar.gz | tar zxv
   mv larvatagger.jl-${LARVATAGGER_BRANCH} LarvaTagger.jl
@@ -155,9 +172,11 @@ EOF
 
 else
 
+[ -z "$internal_WITH_BACKEND" ] && echo "Using environment variable: WITH_BACKEND= $WITH_BACKEND"
+
 activate() {
   # pyenv activation is necessary on WSL
-  command -v pyenv >/dev/null && pyenv local $PYTHON_VERSION
+  command -v pyenv &>/dev/null && pyenv local $PYTHON_VERSION
   poetry env use $PYTHON_VERSION
 }
 
@@ -166,6 +185,8 @@ if [ -d TaggingBackends ]; then
 else
   if [ -z "$TAGGINGBACKENDS_BRANCH" ]; then
     TAGGINGBACKENDS_BRANCH=main
+  else
+    echo "Using environment variable: TAGGINGBACKENDS_BRANCH= $TAGGINGBACKENDS_BRANCH"
   fi
   curl -fsSL https://gitlab.pasteur.fr/nyx/TaggingBackends/-/archive/${TAGGINGBACKENDS_BRANCH}/TaggingBackends-${TAGGINGBACKENDS_BRANCH}.tar.gz | tar zxv
   mv TaggingBackends-${TAGGINGBACKENDS_BRANCH} TaggingBackends
@@ -180,6 +201,7 @@ else
   if [ -z "$MAGGOTUBA_CORE_BRANCH" ]; then
     MAGGOTUBA_CORE_BRANCH=main
   else
+    echo "Using environment variable: MAGGOTUBA_CORE_BRANCH= $MAGGOTUBA_CORE_BRANCH"
     curl -fsSL https://gitlab.pasteur.fr/nyx/maggotuba-core/-/archive/${MAGGOTUBA_CORE_BRANCH}/maggotuba-core-${MAGGOTUBA_CORE_BRANCH}.tar.gz | tar zxv
     mv maggotuba-core-${MAGGOTUBA_CORE_BRANCH} MaggotUBA-core
   fi
@@ -190,6 +212,8 @@ if [ -d MaggotUBA ]; then
 else
   if [ -z "$MAGGOTUBA_ADAPTER_BRANCH" ]; then
     MAGGOTUBA_ADAPTER_BRANCH=main
+  elif [ -z "$internal_MAGGOTUBA_ADAPTER_BRANCH" ]; then
+    echo "Using environment variable: MAGGOTUBA_ADAPTER_BRANCH= $MAGGOTUBA_ADAPTER_BRANCH"
   fi
   curl -fsSL https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter/-/archive/${MAGGOTUBA_ADAPTER_BRANCH}/MaggotUBA-adapter-${MAGGOTUBA_ADAPTER_BRANCH}.tar.gz | tar zxv
   mv MaggotUBA-adapter-${MAGGOTUBA_ADAPTER_BRANCH} MaggotUBA #-adapter
@@ -213,22 +237,29 @@ fi
 
 chmod a+x "$BIN_DIR/larvatagger"
 
-if ! command -v larvatagger >/dev/null; then
-  # ~/.local/bin not in $PATH?
-  if [ "$SHELL" = "/bin/zsh" ]; then
-    rcfile=~/.zshrc
-  elif [ "$SHELL" = "/bin/bash" ]; then
-    if [ -f ~/.bashrc ]; then
-      rcfile=~/.bashrc
-    elif [ -f ~/.bash_profile ]; then
-      rcfile=~/.bash_profile
+if ! command -v larvatagger &>/dev/null; then
+  if [ "`realpath $BIN_DIR`" = "`realpath ~/.local/bin`" ]; then
+    # ~/.local/bin not in $PATH?
+    if [ "$SHELL" = "/bin/zsh" ]; then
+      rcfile=~/.zshrc
+    elif [ "$SHELL" = "/bin/bash" ]; then
+      if [ -f ~/.bashrc ]; then
+        rcfile=~/.bashrc
+      elif [ -f ~/.bash_profile ]; then
+        rcfile=~/.bash_profile
+      fi
     fi
-  fi
-  cat <<"EOF" >>$rcfile
+    echo "Extending the PATH environment variable in $rcfile"
+    cat <<"EOF" >>$rcfile
 
 export PATH=$PATH:~/.local/bin
 EOF
-  export PATH=$PATH:~/.local/bin
+  else
+    echo "the larvatagger command is available in directory:"
+    echo "  $BIN_DIR"
+    echo "consider adding the directory to the PATH variable"
+  fi
+  export PATH=$PATH:$BIN_DIR
 fi
 
 ##
diff --git a/scripts/larvatagger.sh b/scripts/larvatagger.sh
index a261c70..af552f9 100755
--- a/scripts/larvatagger.sh
+++ b/scripts/larvatagger.sh
@@ -45,14 +45,15 @@ if [ -z "$without_rm" ]; then
 fi
 
 if [ -z "$docker" ]; then
-  if docker --version &> /dev/null; then
-    docker=docker
-  else
+  docker=docker
+  if command -v podman &> /dev/null; then
     docker=podman
     if [ "$cmd" != "build" ]; then
       DOCKER_ARGS="${DOCKER_ARGS}--security-opt label=disable "
     fi
   fi
+else
+  echo "Using environment variable: docker= $docker"
 fi
 
 if [ -z "$LARVATAGGER_IMAGE" ]; then
@@ -61,6 +62,8 @@ LARVATAGGER_IMAGE=larvatagger
 else
 LARVATAGGER_IMAGE=flaur/larvatagger
 fi
+else
+echo "Using environment variable: LARVATAGGER_IMAGE= $LARVATAGGER_IMAGE"
 fi
 
 HOST_UID=$(id -u $USER)
@@ -104,7 +107,10 @@ DOCKER_ARGS="--target $TARGET $DOCKER_ARGS"
 fi
 if [ -z "$DOCKERFILE" ]; then
   DOCKERFILE=recipes/Dockerfile
+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))
@@ -112,7 +118,7 @@ cd ..
 DOCKER_BUILDKIT=1 $docker build -t "$LARVATAGGER_IMAGE" -f "$PROJECT_ROOT/recipes/Dockerfile.local" ${DOCKER_ARGS}.
 elif [ "$BUILD" == "--stable" ]; then
 if ! [[ "$LARVATAGGER_IMAGE" == *:* ]]; then LARVATAGGER_IMAGE="${LARVATAGGER_IMAGE}:stable"; fi
-$docker build -t "$LARVATAGGER_IMAGE" -f recipes/Dockerfile ${DOCKER_ARGS}.
+$docker build -t "$LARVATAGGER_IMAGE" ${DOCKER_ARGS}.
 else
 if ! [[ "$LARVATAGGER_IMAGE" == *:* ]]; then LARVATAGGER_IMAGE="${LARVATAGGER_IMAGE}:latest"; fi
 if [ -z "$LARVATAGGER_BRANCH" ]; then
@@ -122,12 +128,16 @@ if [ -z "$LARVATAGGER_BRANCH" ]; then
     echo "Deprecation notice: LARVATAGGER_DEFAULT_BRANCH has been renamed LARVATAGGER_BRANCH"
     LARVATAGGER_BRANCH=$LARVATAGGER_DEFAULT_BRANCH
   fi
+else
+  echo "Using environment variable: LARVATAGGER_BRANCH= $LARVATAGGER_BRANCH"
 fi
 if [ -z "$TAGGINGBACKENDS_BRANCH" ]; then
   TAGGINGBACKENDS_BRANCH=$LARVATAGGER_BRANCH
+else
+  echo "Using environment variable: TAGGINGBACKENDS_BRANCH= $TAGGINGBACKENDS_BRANCH"
 fi
 DOCKER_ARGS="--build-arg TAGGINGBACKENDS_BRANCH=$TAGGINGBACKENDS_BRANCH $DOCKER_ARGS"
-$docker build -t "$LARVATAGGER_IMAGE" -f $DOCKERFILE ${DOCKER_ARGS}--build-arg BRANCH=$LARVATAGGER_BRANCH .
+$docker build -t "$LARVATAGGER_IMAGE" ${DOCKER_ARGS}--build-arg BRANCH=$LARVATAGGER_BRANCH .
 fi
 ;;
 
@@ -137,6 +147,7 @@ if [ -z "$LARVATAGGER_PORT" -o "$LARVATAGGER_PORT" == "9284" ]; then
 DOCKER_ARGS="-p 9284:9284 $DOCKER_ARGS"
 TAGGER_ARGS=
 else
+echo "Using environment variable: LARVATAGGER_PORT= $LARVATAGGER_PORT"
 DOCKER_ARGS="-p $LARVATAGGER_PORT:$LARVATAGGER_PORT $DOCKER_ARGS"
 TAGGER_ARGS="--port=$LARVATAGGER_PORT"
 fi
@@ -150,7 +161,7 @@ RUN_ARGS="$RUN_ARGS -v \"$parentdir\":/data"
 backend=MaggotUBA
 while [ -n "$1" -a "$1" = "--external-instance" ]; do
 instance=$2; shift 2
-if [ -z "$(which realpath)" ]; then
+if ! command -v realpath &>/dev/null; then
 echo "realpath: command not found"
 echo "on macOS: brew install coreutils"
 exit 1
@@ -269,7 +280,7 @@ fi
 done
 
 if [ -n "$tagger_path" ]; then
-if [ -z "$(which realpath)" ]; then
+if ! command -v realpath &>/dev/null; then
 echo "realpath: command not found"
 echo "on macOS: brew install coreutils"
 exit 1
-- 
GitLab


From ba42e681640ab3e9467d7d966dec00f2f832d477 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Tue, 12 Nov 2024 22:27:46 +0100
Subject: [PATCH 08/18] feat(install.sh): --free-python-dependencies flag

---
 scripts/install.sh  | 194 ++++++++++++++++++++++++++++++++------------
 scripts/larvatagger |   4 +-
 2 files changed, 144 insertions(+), 54 deletions(-)

diff --git a/scripts/install.sh b/scripts/install.sh
index fead1e9..994b588 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -6,6 +6,7 @@ if [ -z "$BIN_DIR" ]; then
   BIN_DIR=~/.local/bin
 else
   echo "Using environment variable: BIN_DIR= $BIN_DIR"
+  echo "Only ~/.local/bin is fully supported at the moment"
 fi
 if [ -z "$LARVATAGGER_PATH" ]; then
   LARVATAGGER_PATH=~/.local/share/larvatagger
@@ -22,6 +23,21 @@ if [ "$1" = "--uninstall" ]; then
   done
   rm -rf "$BIN_DIR/larvatagger"
   rm -rf "$LARVATAGGER_PATH"
+
+  # testing only; does not apply to all platforms; do not pass the --full option!
+  if [ "$2" = "--full" ]; then
+    PYTHON="python3"
+    if [[ "`python3 -V`" =~ "Python 3.6" ]] && command -v python3.8 &>/dev/null; then
+      # issue on Maestro
+      PYTHON="python3.8"
+    fi
+    command -v poetry &>/dev/null && curl -sSL https://install.python-poetry.org | $PYTHON - --uninstall
+    command -v pyenv &>/dev/null && rm -rf $(pyenv root)
+    command -v juliaup &>/dev/null && juliaup self uninstall
+    rm -rf ~/.juliaup
+    # TODO: clean up .bash_profile for pyenv-related stuff and restart the shell
+    rm -rf ~/.julia
+  fi
 else
 
 PYTHON_VERSION=3.8
@@ -30,6 +46,7 @@ PYTHON_VERSION=3.8
 # determine whether or not to report externally sourced variables
 internal_WITH_BACKEND=
 internal_MAGGOTUBA_ADAPTER_BRANCH=
+internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=
 for arg in "$@"; do
   if [ "$arg" = "--with-default-backend" ]; then
     WITH_BACKEND=1
@@ -45,9 +62,13 @@ for arg in "$@"; do
     MAGGOTUBA_ADAPTER_BRANCH=torch2
     internal_MAGGOTUBA_ADAPTER_BRANCH=1
     PYTHON_VERSION=3.11
+  elif [ "$arg" = "--free-python-dependencies" ]; then
+    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=1
   fi
 done
 
+PYTHON="python$PYTHON_VERSION"
+
 if ! command -v curl &>/dev/null; then
   if command -v brew &>/dev/null; then
     # macOS users are not given the choice as they usually do not care about freedom
@@ -76,20 +97,97 @@ if ! command -v realpath &>/dev/null; then
   fi
 fi
 
-if ! command -v julia &>/dev/null; then
+if [ -z "$JULIA_VERSION" ]; then
+  JULIA_VERSION=1.10
+else
+  echo "Using environment variable: JULIA_VERSION= $JULIA_VERSION"
+fi
+
+JULIA="julia"
+
+install_juliaup() {
   if [ -z "$JULIA_INSTALL_ARGS" ]; then
     JULIA_INSTALL_ARGS=-y
   else
     echo "Using environment variable: JULIA_INSTALL_ARGS= $JULIA_INSTALL_ARGS"
   fi
-  curl -fsSL https://install.julialang.org | sh -s -- $JULIA_INSTALL_ARGS
-  if [ -f ~/.bashrc ]; then
+  curl -fsSL https://install.julialang.org | sh -s -- $JULIA_INSTALL_ARGS --default-channel $JULIA_VERSION
+  if [ -f ~/.bashrc -a -n "`grep "# >>> juliaup initialize >>>" ~/.bashrc`" ]; then
     source ~/.bashrc
-  elif [ -f ~/.bash_profile ]; then
+  elif [ -f ~/.bash_profile -a -n "`grep "# >>> juliaup initialize >>>" ~/.bash_profile`" ]; then
     source ~/.bash_profile
   fi
+}
+if ! command -v $JULIA &>/dev/null; then
+  install_juliaup
+elif ! [[ "`$JULIA -v`" =~ "julia version $JULIA_VERSION" ]]; then
+  install_juliaup
 fi
 
+add_local_bin_to_path() {
+  if [ "`realpath $BIN_DIR`" = "`realpath ~/.local/bin`" ]; then
+    # ~/.local/bin not in $PATH?
+    if [ "$SHELL" = "/bin/zsh" ]; then
+      rcfile=~/.zshrc
+    elif [ "$SHELL" = "/bin/bash" ]; then
+      if [ -f ~/.bashrc ]; then
+        rcfile=~/.bashrc
+      elif [ -f ~/.bash_profile ]; then
+        rcfile=~/.bash_profile
+      fi
+    fi
+    echo "Extending the PATH environment variable in $rcfile"
+    cat <<"EOF" >>$rcfile
+
+export PATH=$PATH:~/.local/bin
+EOF
+  else
+    echo "the larvatagger command is available in directory:"
+    echo "  $BIN_DIR"
+    echo "consider adding the directory to the PATH variable"
+  fi
+  export PATH=$PATH:$BIN_DIR
+}
+
+install_pyenv_on_ubuntu2004() {
+  echo
+  echo "INFO: installing pyenv and its dependencies"
+  echo
+  if ! [ -d ~/.pyenv ]; then
+    sudo apt-get update
+    sudo apt-get install --no-install-recommends curl build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev llvm libncurses5-dev xz-utils libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev #tk-dev
+    curl https://pyenv.run | bash
+  fi
+  export PATH=~/.pyenv/bin:$PATH
+  eval "$(~/.pyenv/bin/pyenv init -)"
+  cat <<"EOF" >>~/.bash_profile
+
+command -v pyenv &>/dev/null || export PATH=~/.pyenv/bin:$PATH
+eval "$(~/.pyenv/bin/pyenv init -)"
+EOF
+  pyenv install $PYTHON_VERSION
+}
+
+install_pyenv() {
+  echo
+  echo "INFO: installing pyenv"
+  echo
+  if ! [ -d ~/.pyenv ]; then
+    curl https://pyenv.run | bash
+  fi
+  if ! command -v pyenv &>/dev/null; then
+    export PATH=~/.pyenv/bin:$PATH
+    eval "$(~/.pyenv/bin/pyenv init -)"
+    cat <<"EOF" >>~/.bash_profile
+
+command -v pyenv &>/dev/null || export PATH=~/.pyenv/bin:$PATH
+eval "$(~/.pyenv/bin/pyenv init -)"
+EOF
+  fi
+  pyenv install $PYTHON_VERSION
+  pyenv shell $PYTHON_VERSION
+}
+
 if [ -n "$WITH_BACKEND" ]; then
   if [ "`uname`" = "Darwin" ]; then
     echo "WARNING: the default tagging backend is not supported by macOS"
@@ -98,28 +196,18 @@ if [ -n "$WITH_BACKEND" ]; then
     if command -v pyenv &>/dev/null; then
       [ `pyenv versions | grep $PYTHON_VERSION` ] || pyenv install $PYTHON_VERSION
     elif [[ "`uname -r`" =~ "-microsoft-standard-WSL2" ]]; then
-      echo
-      echo "INFO: installing pyenv and its dependencies"
-      echo
-      if ! [ -d ~/.pyenv ]; then
-        sudo apt-get update
-        sudo apt-get install --no-install-recommends curl build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev llvm libncurses5-dev xz-utils libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev #tk-dev
-        curl https://pyenv.run | bash
-      fi
-      export PATH=~/.pyenv/bin:$PATH
-      eval "$(~/.pyenv/bin/pyenv init -)"
-      cat <<"EOF" >>~/.bashrc
-
-command -v pyenv &>/dev/null || export PATH=~/.pyenv/bin:$PATH
-eval "$(~/.pyenv/bin/pyenv init -)"
-EOF
-      pyenv install $PYTHON_VERSION
+      install_pyenv_on_ubuntu2004
+    elif [[ "`hostname`" =~ ".cluster.embl.de" ]]; then
+      install_pyenv
+    elif [ "`hostname`" = "maestro-submit" ]; then
+      # we could use `module load Python/<version>` but modules are removed without notice
+      install_pyenv
     else
       check_brew
       if command -v brew &>/dev/null; then
         brew install python@$PYTHON_VERSION
       else
-        echo "WARNING: command python$PYTHON_VERSION not found"
+        echo "WARNING: command $PYTHON not found"
       fi
     fi
   fi
@@ -127,7 +215,9 @@ EOF
     if command -v pipx &>/dev/null; then
       pipx install poetry
     else
-      curl -fsSL https://install.python-poetry.org | python3 -
+      # explicit Python version for Maestro
+      curl -fsSL https://install.python-poetry.org | $PYTHON -
+      command -v poetry &>/dev/null || add_local_bin_to_path
     fi
   fi
 fi
@@ -158,8 +248,8 @@ else
   curl -fsSL https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/archive/${LARVATAGGER_BRANCH}/larvatagger.jl-${LARVATAGGER_BRANCH}.tar.gz | tar zxv
   mv larvatagger.jl-${LARVATAGGER_BRANCH} LarvaTagger.jl
 fi
-(cd LarvaTagger.jl && julia --project=. -e 'using Pkg; Pkg.instantiate()')
-[ -d PlanarLarvae ] && (cd LarvaTagger.jl && julia --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
+(cd LarvaTagger.jl && $JULIA --project=. -e 'using Pkg; Pkg.instantiate()')
+[ -d PlanarLarvae ] && (cd LarvaTagger.jl && $JULIA --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
 
 if [ -z "$WITH_BACKEND" ]; then
 
@@ -167,7 +257,16 @@ mkdir -p "$BIN_DIR"
 cat <<EOF >"$BIN_DIR"/larvatagger
 #!/usr/bin/env bash
 
+if command -v juliaup &>/dev/null; then
+  PREVIOUS_CHANNEL=\`juliaup status | grep '*' | cut -d'*' -f2 | cut -d\\  -f3\`
+  juliaup default $JULIA_VERSION &>/dev/null
+fi
+
 OPEN_BROWSER=1 "$LARVATAGGER_PATH/LarvaTagger.jl/scripts/larvatagger" \$@
+
+if command -v juliaup &>/dev/null; then
+  juliaup default \$PREVIOUS_CHANNEL &>/dev/null
+fi
 EOF
 
 else
@@ -191,8 +290,8 @@ else
   curl -fsSL https://gitlab.pasteur.fr/nyx/TaggingBackends/-/archive/${TAGGINGBACKENDS_BRANCH}/TaggingBackends-${TAGGINGBACKENDS_BRANCH}.tar.gz | tar zxv
   mv TaggingBackends-${TAGGINGBACKENDS_BRANCH} TaggingBackends
 fi
-(cd TaggingBackends && julia --project=. -e 'using Pkg; Pkg.instantiate()')
-[ -d PlanarLarvae ] && (cd TaggingBackends && julia --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
+(cd TaggingBackends && activate && PYTHON=`command -v python` && $JULIA --project=. -e 'using Pkg; Pkg.instantiate()')
+[ -d PlanarLarvae ] && (cd TaggingBackends && $JULIA --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
 (cd TaggingBackends && activate && JULIA_PROJECT=$(pwd) poetry install)
 
 if [ -d MaggotUBA-core ]; then
@@ -221,46 +320,37 @@ else
 fi
 # setting JULIA_PROJECT may not be necessary at this point
 export JULIA_PROJECT=$(realpath TaggingBackends)
-(cd MaggotUBA && activate && (cat requirements.txt | xargs -I % sh -c 'poetry add "%"' || true) && poetry install -v)
+
+if [ -z "$internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES" ]; then
+  (cd MaggotUBA && activate && (cat requirements.txt | xargs -I % sh -c 'poetry add "%"' || true) && poetry install -v)
+else
+  (cd MaggotUBA && activate && poetry install -v)
+fi
 [ -d TaggingBackends ] && (cd MaggotUBA && activate && poetry remove taggingbackends && poetry add ../TaggingBackends)
 [ -d MaggotUBA-core ] && (cd MaggotUBA && activate && poetry remove maggotuba-core && poetry add ../MaggotUBA-core)
-(cd MaggotUBA && scripts/make_models.jl default)
+(cd MaggotUBA && scripts/make_models.jl default) # julia version does not matter here
 
 mkdir -p "$BIN_DIR"
 cat <<EOF >"$BIN_DIR"/larvatagger
 #!/usr/bin/env bash
 
+if command -v juliaup &>/dev/null; then
+  PREVIOUS_CHANNEL=\`juliaup status | grep '*' | cut -d'*' -f2 | cut -d\\  -f3\`
+  juliaup default $JULIA_VERSION &>/dev/null
+fi
+
 JULIA_PROJECT="$(realpath "$LARVATAGGER_PATH/TaggingBackends")" BACKENDS_PATH="$LARVATAGGER_PATH" OPEN_BROWSER=1 "$LARVATAGGER_PATH/LarvaTagger.jl/scripts/larvatagger" \$@
+
+if command -v juliaup &>/dev/null; then
+  juliaup default \$PREVIOUS_CHANNEL &>/dev/null
+fi
 EOF
 
 fi
 
 chmod a+x "$BIN_DIR/larvatagger"
 
-if ! command -v larvatagger &>/dev/null; then
-  if [ "`realpath $BIN_DIR`" = "`realpath ~/.local/bin`" ]; then
-    # ~/.local/bin not in $PATH?
-    if [ "$SHELL" = "/bin/zsh" ]; then
-      rcfile=~/.zshrc
-    elif [ "$SHELL" = "/bin/bash" ]; then
-      if [ -f ~/.bashrc ]; then
-        rcfile=~/.bashrc
-      elif [ -f ~/.bash_profile ]; then
-        rcfile=~/.bash_profile
-      fi
-    fi
-    echo "Extending the PATH environment variable in $rcfile"
-    cat <<"EOF" >>$rcfile
-
-export PATH=$PATH:~/.local/bin
-EOF
-  else
-    echo "the larvatagger command is available in directory:"
-    echo "  $BIN_DIR"
-    echo "consider adding the directory to the PATH variable"
-  fi
-  export PATH=$PATH:$BIN_DIR
-fi
+command -v larvatagger &>/dev/null || add_local_bin_to_path
 
 ##
 popd
diff --git a/scripts/larvatagger b/scripts/larvatagger
index db34a6c..b46c90c 100755
--- a/scripts/larvatagger
+++ b/scripts/larvatagger
@@ -23,10 +23,10 @@ open)
   shift
   ltargs=
   if [ -n "$BACKENDS_PATH" ]; then
-    ltargs="--backends=\"$BACKENDS_PATH\" $args"
+    ltargs="--backends=\"$BACKENDS_PATH\" $ltargs"
   fi
   if [ -n "$OPEN_BROWSER" ]; then
-    ltargs="--browser $args"
+    ltargs="--browser $ltargs"
   fi
   eval "\"$currentdir/larvatagger-gui.jl\" $jlargs\"$datapath\" $ltargs$@"
   ;;
-- 
GitLab


From 711875b93b2cd7796c6c11d00763467c38660b38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20=20LAURENT?= <francois.laurent@pasteur.fr>
Date: Sun, 17 Nov 2024 11:42:13 +0100
Subject: [PATCH 09/18] Implement issues 10 and 57

---
 .gitlab-ci.yml             |    9 +-
 Manifest-v1.11.toml        | 1756 ++++++++++++++++++++++++++++++++++++
 Manifest.toml              |  725 ++++++++-------
 Project.toml               |   10 +-
 README.md                  |    1 +
 scripts/larvatagger        |    3 +-
 scripts/larvatagger-gui.jl |    4 +-
 src/LarvaTagger.jl         |    8 +-
 src/cli.jl                 |    3 +-
 src/cli_open.jl            |   21 +-
 src/controllers.jl         |   14 +-
 src/editor.jl              |   16 +-
 src/files.jl               |   91 +-
 src/larvatagger.css        |   22 +-
 src/larvatagger.js         |   43 +-
 src/models.jl              |    6 +-
 src/players.jl             |  277 +-----
 src/plots.jl               |   32 +-
 src/viewer.jl              |   87 +-
 src/wgl.jl                 |  904 ++++++++-----------
 20 files changed, 2794 insertions(+), 1238 deletions(-)
 create mode 100644 Manifest-v1.11.toml

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8d36e3b..b90f3eb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,8 +16,11 @@
         c, t = get_summary(process_folder())
         using Printf
         @printf "Test coverage %.2f%%\n" 100c / t'
-Julia 1.9:
-  image: julia:1.9
+Julia 1.10:
+  image: julia:1.10
+  extends:
+    - .script
+Julia 1.11:
+  image: julia:1.11
   extends:
     - .script
-    - .coverage
diff --git a/Manifest-v1.11.toml b/Manifest-v1.11.toml
new file mode 100644
index 0000000..f2d4c37
--- /dev/null
+++ b/Manifest-v1.11.toml
@@ -0,0 +1,1756 @@
+# This file is machine-generated - editing it directly is not advised
+
+julia_version = "1.11.1"
+manifest_format = "2.0"
+project_hash = "fccf84810b56f9ba8947369bbaf414d318bc47f2"
+
+[[deps.AbstractFFTs]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "d92ad398961a3ed262d8bf04a1a2b8340f915fef"
+uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
+version = "1.5.0"
+weakdeps = ["ChainRulesCore", "Test"]
+
+    [deps.AbstractFFTs.extensions]
+    AbstractFFTsChainRulesCoreExt = "ChainRulesCore"
+    AbstractFFTsTestExt = "Test"
+
+[[deps.AbstractTrees]]
+git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177"
+uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
+version = "0.4.5"
+
+[[deps.Adapt]]
+deps = ["LinearAlgebra", "Requires"]
+git-tree-sha1 = "50c3c56a52972d78e8be9fd135bfb91c9574c140"
+uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
+version = "4.1.1"
+weakdeps = ["StaticArrays"]
+
+    [deps.Adapt.extensions]
+    AdaptStaticArraysExt = "StaticArrays"
+
+[[deps.AdaptivePredicates]]
+git-tree-sha1 = "7e651ea8d262d2d74ce75fdf47c4d63c07dba7a6"
+uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7"
+version = "1.2.0"
+
+[[deps.AliasTables]]
+deps = ["PtrArrays", "Random"]
+git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff"
+uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"
+version = "1.1.3"
+
+[[deps.Animations]]
+deps = ["Colors"]
+git-tree-sha1 = "e81c509d2c8e49592413bfb0bb3b08150056c79d"
+uuid = "27a7e980-b3e6-11e9-2bcd-0b925532e340"
+version = "0.4.1"
+
+[[deps.ArgTools]]
+uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
+version = "1.1.2"
+
+[[deps.Artifacts]]
+uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
+version = "1.11.0"
+
+[[deps.Automa]]
+deps = ["PrecompileTools", "SIMD", "TranscodingStreams"]
+git-tree-sha1 = "a8f503e8e1a5f583fbef15a8440c8c7e32185df2"
+uuid = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b"
+version = "1.1.0"
+
+[[deps.AxisAlgorithms]]
+deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
+git-tree-sha1 = "01b8ccb13d68535d73d2b0c23e39bd23155fb712"
+uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950"
+version = "1.1.0"
+
+[[deps.AxisArrays]]
+deps = ["Dates", "IntervalSets", "IterTools", "RangeArrays"]
+git-tree-sha1 = "16351be62963a67ac4083f748fdb3cca58bfd52f"
+uuid = "39de3d68-74b9-583c-8d2d-e117c070f3a9"
+version = "0.4.7"
+
+[[deps.Base64]]
+uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
+version = "1.11.0"
+
+[[deps.Bessels]]
+git-tree-sha1 = "4435559dc39793d53a9e3d278e185e920b4619ef"
+uuid = "0e736298-9ec6-45e8-9647-e4fc86a2fe38"
+version = "0.2.8"
+
+[[deps.BitFlags]]
+git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d"
+uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35"
+version = "0.1.9"
+
+[[deps.Bonito]]
+deps = ["Base64", "CodecZlib", "Colors", "Dates", "Deno_jll", "HTTP", "Hyperscript", "LinearAlgebra", "Markdown", "MsgPack", "Observables", "RelocatableFolders", "SHA", "Sockets", "Tables", "ThreadPools", "URIs", "UUIDs", "WidgetsBase"]
+git-tree-sha1 = "d7635780a8cfe0cb43c075276fd358c5b166695e"
+uuid = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
+version = "3.2.4"
+
+[[deps.BufferedStreams]]
+git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643"
+uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
+version = "1.2.2"
+
+[[deps.Bzip2_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "8873e196c2eb87962a2048b3b8e08946535864a1"
+uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
+version = "1.0.8+2"
+
+[[deps.CEnum]]
+git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
+uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
+version = "0.5.0"
+
+[[deps.CRC32c]]
+uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc"
+version = "1.11.0"
+
+[[deps.CRlibm_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc"
+uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0"
+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"
+uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
+version = "1.18.2+1"
+
+[[deps.ChainRulesCore]]
+deps = ["Compat", "LinearAlgebra"]
+git-tree-sha1 = "3e4b134270b372f2ed4d4d0e936aabaefc1802bc"
+uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+version = "1.25.0"
+weakdeps = ["SparseArrays"]
+
+    [deps.ChainRulesCore.extensions]
+    ChainRulesCoreSparseArraysExt = "SparseArrays"
+
+[[deps.CircularArrays]]
+deps = ["OffsetArrays"]
+git-tree-sha1 = "e24a6f390e5563583bb4315c73035b5b3f3e7ab4"
+uuid = "7a955b69-7140-5f4e-a0ed-f168c5e2e749"
+version = "1.4.0"
+
+[[deps.CodecZlib]]
+deps = ["TranscodingStreams", "Zlib_jll"]
+git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759"
+uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
+version = "0.7.6"
+
+[[deps.ColorBrewer]]
+deps = ["Colors", "JSON", "Test"]
+git-tree-sha1 = "61c5334f33d91e570e1d0c3eb5465835242582c4"
+uuid = "a2cac450-b92f-5266-8821-25eda20663c8"
+version = "0.4.0"
+
+[[deps.ColorSchemes]]
+deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"]
+git-tree-sha1 = "13951eb68769ad1cd460cdb2e64e5e95f1bf123d"
+uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
+version = "3.27.0"
+
+[[deps.ColorTypes]]
+deps = ["FixedPointNumbers", "Random"]
+git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d"
+uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
+version = "0.11.5"
+
+[[deps.ColorVectorSpace]]
+deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"]
+git-tree-sha1 = "a1f44953f2382ebb937d60dafbe2deea4bd23249"
+uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
+version = "0.10.0"
+weakdeps = ["SpecialFunctions"]
+
+    [deps.ColorVectorSpace.extensions]
+    SpecialFunctionsExt = "SpecialFunctions"
+
+[[deps.Colors]]
+deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
+git-tree-sha1 = "362a287c3aa50601b0bc359053d5c2468f0e7ce0"
+uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
+version = "0.12.11"
+
+[[deps.Compat]]
+deps = ["TOML", "UUIDs"]
+git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215"
+uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
+version = "4.16.0"
+weakdeps = ["Dates", "LinearAlgebra"]
+
+    [deps.Compat.extensions]
+    CompatLinearAlgebraExt = "LinearAlgebra"
+
+[[deps.CompilerSupportLibraries_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
+version = "1.1.1+0"
+
+[[deps.ConcurrentUtilities]]
+deps = ["Serialization", "Sockets"]
+git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1"
+uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
+version = "2.4.2"
+
+[[deps.ConstructionBase]]
+git-tree-sha1 = "76219f1ed5771adbb096743bff43fb5fdd4c1157"
+uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
+version = "1.5.8"
+weakdeps = ["IntervalSets", "LinearAlgebra", "StaticArrays"]
+
+    [deps.ConstructionBase.extensions]
+    ConstructionBaseIntervalSetsExt = "IntervalSets"
+    ConstructionBaseLinearAlgebraExt = "LinearAlgebra"
+    ConstructionBaseStaticArraysExt = "StaticArrays"
+
+[[deps.Contour]]
+git-tree-sha1 = "439e35b0b36e2e5881738abc8857bd92ad6ff9a8"
+uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
+version = "0.6.3"
+
+[[deps.DataAPI]]
+git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe"
+uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
+version = "1.16.0"
+
+[[deps.DataStructures]]
+deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
+git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82"
+uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
+version = "0.18.20"
+
+[[deps.DataValueInterfaces]]
+git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
+uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
+version = "1.0.0"
+
+[[deps.Dates]]
+deps = ["Printf"]
+uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
+version = "1.11.0"
+
+[[deps.DelaunayTriangulation]]
+deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "PrecompileTools", "Random"]
+git-tree-sha1 = "89df54fbe66e5872d91d8c2cd3a375f660c3fd64"
+uuid = "927a84f5-c5f4-47a5-9785-b46e178433df"
+version = "1.6.1"
+
+[[deps.DelimitedFiles]]
+deps = ["Mmap"]
+git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae"
+uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
+version = "1.9.1"
+
+[[deps.Deno_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "cd6756e833c377e0ce9cd63fb97689a255f12323"
+uuid = "04572ae6-984a-583e-9378-9577a1c2574d"
+version = "1.33.4+0"
+
+[[deps.Distances]]
+deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
+git-tree-sha1 = "c7e3a542b999843086e2f29dac96a618c105be1d"
+uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
+version = "0.10.12"
+weakdeps = ["ChainRulesCore", "SparseArrays"]
+
+    [deps.Distances.extensions]
+    DistancesChainRulesCoreExt = "ChainRulesCore"
+    DistancesSparseArraysExt = "SparseArrays"
+
+[[deps.Distributed]]
+deps = ["Random", "Serialization", "Sockets"]
+uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
+version = "1.11.0"
+
+[[deps.Distributions]]
+deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
+git-tree-sha1 = "3101c32aab536e7a27b1763c0797dba151b899ad"
+uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
+version = "0.25.113"
+
+    [deps.Distributions.extensions]
+    DistributionsChainRulesCoreExt = "ChainRulesCore"
+    DistributionsDensityInterfaceExt = "DensityInterface"
+    DistributionsTestExt = "Test"
+
+    [deps.Distributions.weakdeps]
+    ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+    DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
+    Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[deps.DocOpt]]
+deps = ["Dates", "Printf"]
+git-tree-sha1 = "9c2681389ed8d9cf6193b1ba31796f12456b111a"
+uuid = "968ba79b-81e4-546f-ab3a-2eecfa62a9db"
+version = "0.5.0"
+
+[[deps.DocStringExtensions]]
+deps = ["LibGit2"]
+git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d"
+uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
+version = "0.9.3"
+
+[[deps.Downloads]]
+deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
+uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
+version = "1.6.0"
+
+[[deps.EarCut_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "e3290f2d49e661fbd94046d7e3726ffcb2d41053"
+uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
+version = "2.2.4+0"
+
+[[deps.EnumX]]
+git-tree-sha1 = "bdb1942cd4c45e3c678fd11569d5cccd80976237"
+uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
+version = "1.0.4"
+
+[[deps.ExactPredicates]]
+deps = ["IntervalArithmetic", "Random", "StaticArrays"]
+git-tree-sha1 = "b3f2ff58735b5f024c392fde763f29b057e4b025"
+uuid = "429591f6-91af-11e9-00e2-59fbe8cec110"
+version = "2.2.8"
+
+[[deps.ExceptionUnwrapping]]
+deps = ["Test"]
+git-tree-sha1 = "dcb08a0d93ec0b1cdc4af184b26b591e9695423a"
+uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4"
+version = "0.1.10"
+
+[[deps.Expat_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7"
+uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
+version = "2.6.2+0"
+
+[[deps.Extents]]
+git-tree-sha1 = "81023caa0021a41712685887db1fc03db26f41f5"
+uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
+version = "0.1.4"
+
+[[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"]
+git-tree-sha1 = "8cc47f299902e13f90405ddb5bf87e5d474c0d38"
+uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
+version = "6.1.2+0"
+
+[[deps.FFTW]]
+deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
+git-tree-sha1 = "4820348781ae578893311153d69049a93d05f39d"
+uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
+version = "1.8.0"
+
+[[deps.FFTW_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "4d81ed14783ec49ce9f2e168208a12ce1815aa25"
+uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
+version = "3.3.10+1"
+
+[[deps.FileIO]]
+deps = ["Pkg", "Requires", "UUIDs"]
+git-tree-sha1 = "62ca0547a14c57e98154423419d8a342dca75ca9"
+uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
+version = "1.16.4"
+
+[[deps.FilePaths]]
+deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"]
+git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629"
+uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824"
+version = "0.8.3"
+
+[[deps.FilePathsBase]]
+deps = ["Compat", "Dates"]
+git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361"
+uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
+version = "0.9.22"
+weakdeps = ["Mmap", "Test"]
+
+    [deps.FilePathsBase.extensions]
+    FilePathsBaseMmapExt = "Mmap"
+    FilePathsBaseTestExt = "Test"
+
+[[deps.FileWatching]]
+uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
+version = "1.11.0"
+
+[[deps.FillArrays]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "6a70198746448456524cb442b8af316927ff3e1a"
+uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
+version = "1.13.0"
+weakdeps = ["PDMats", "SparseArrays", "Statistics"]
+
+    [deps.FillArrays.extensions]
+    FillArraysPDMatsExt = "PDMats"
+    FillArraysSparseArraysExt = "SparseArrays"
+    FillArraysStatisticsExt = "Statistics"
+
+[[deps.FixedPointNumbers]]
+deps = ["Statistics"]
+git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172"
+uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
+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"
+uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
+version = "2.13.96+0"
+
+[[deps.Format]]
+git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc"
+uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
+version = "1.3.7"
+
+[[deps.FreeType]]
+deps = ["CEnum", "FreeType2_jll"]
+git-tree-sha1 = "907369da0f8e80728ab49c1c7e09327bf0d6d999"
+uuid = "b38be410-82b0-50bf-ab77-7b57e271db43"
+version = "4.1.1"
+
+[[deps.FreeType2_jll]]
+deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc"
+uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
+version = "2.13.2+0"
+
+[[deps.FreeTypeAbstraction]]
+deps = ["ColorVectorSpace", "Colors", "FreeType", "GeometryBasics"]
+git-tree-sha1 = "84dfe824bd6fdf2a5d73bb187ff31b5549b2a79c"
+uuid = "663a7486-cb36-511b-a19d-713bb74d65c9"
+version = "0.10.4"
+
+[[deps.FriBidi_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1ed150b39aebcc805c26b93a8d0122c940f64ce2"
+uuid = "559328eb-81f9-559d-9380-de523a88c83c"
+version = "1.0.14+0"
+
+[[deps.GeoFormatTypes]]
+git-tree-sha1 = "59107c179a586f0fe667024c5eb7033e81333271"
+uuid = "68eda718-8dee-11e9-39e7-89f7f65f511f"
+version = "0.4.2"
+
+[[deps.GeoInterface]]
+deps = ["Extents", "GeoFormatTypes"]
+git-tree-sha1 = "826b4fd69438d9ce4d2b19de6bc2f970f45f0f88"
+uuid = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
+version = "1.3.8"
+
+[[deps.GeometryBasics]]
+deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
+git-tree-sha1 = "b62f2b2d76cee0d61a2ef2b3118cd2a3215d3134"
+uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
+version = "0.4.11"
+
+[[deps.Gettext_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
+git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
+uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
+version = "0.21.0+0"
+
+[[deps.Giflib_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "0224cce99284d997f6880a42ef715a37c99338d1"
+uuid = "59f7168a-df46-5410-90c8-f2779963d0ec"
+version = "5.2.2+0"
+
+[[deps.Glib_jll]]
+deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"]
+git-tree-sha1 = "674ff0db93fffcd11a3573986e550d66cd4fd71f"
+uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
+version = "2.80.5+0"
+
+[[deps.Graphite2_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011"
+uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
+version = "1.3.14+0"
+
+[[deps.GridLayoutBase]]
+deps = ["GeometryBasics", "InteractiveUtils", "Observables"]
+git-tree-sha1 = "fc713f007cff99ff9e50accba6373624ddd33588"
+uuid = "3955a311-db13-416c-9275-1d80ed98e5e9"
+version = "0.11.0"
+
+[[deps.Grisu]]
+git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
+uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
+version = "1.0.2"
+
+[[deps.HDF5]]
+deps = ["Compat", "HDF5_jll", "Libdl", "MPIPreferences", "Mmap", "Preferences", "Printf", "Random", "Requires", "UUIDs"]
+git-tree-sha1 = "e856eef26cf5bf2b0f95f8f4fc37553c72c8641c"
+uuid = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
+version = "0.17.2"
+
+    [deps.HDF5.extensions]
+    MPIExt = "MPI"
+
+    [deps.HDF5.weakdeps]
+    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"
+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 = "bc3f416a965ae61968c20d0ad867556367f2817d"
+uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
+version = "1.10.9"
+
+[[deps.HarfBuzz_jll]]
+deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"]
+git-tree-sha1 = "401e4f3f30f43af2c8478fc008da50096ea5240f"
+uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
+version = "8.3.1+0"
+
+[[deps.Hwloc_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "dd3b49277ec2bb2c6b94eb1604d4d0616016f7a6"
+uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8"
+version = "2.11.2+0"
+
+[[deps.HypergeometricFunctions]]
+deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
+git-tree-sha1 = "7c4195be1649ae622304031ed46a2f4df989f1eb"
+uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
+version = "0.3.24"
+
+[[deps.Hyperscript]]
+deps = ["Test"]
+git-tree-sha1 = "179267cfa5e712760cd43dcae385d7ea90cc25a4"
+uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
+version = "0.0.5"
+
+[[deps.ImageAxes]]
+deps = ["AxisArrays", "ImageBase", "ImageCore", "Reexport", "SimpleTraits"]
+git-tree-sha1 = "2e4520d67b0cef90865b3ef727594d2a58e0e1f8"
+uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac"
+version = "0.6.11"
+
+[[deps.ImageBase]]
+deps = ["ImageCore", "Reexport"]
+git-tree-sha1 = "eb49b82c172811fd2c86759fa0553a2221feb909"
+uuid = "c817782e-172a-44cc-b673-b171935fbb9e"
+version = "0.1.7"
+
+[[deps.ImageCore]]
+deps = ["ColorVectorSpace", "Colors", "FixedPointNumbers", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "PrecompileTools", "Reexport"]
+git-tree-sha1 = "b2a7eaa169c13f5bcae8131a83bc30eff8f71be0"
+uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
+version = "0.10.2"
+
+[[deps.ImageIO]]
+deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs", "WebP"]
+git-tree-sha1 = "696144904b76e1ca433b886b4e7edd067d76cbf7"
+uuid = "82e4d734-157c-48bb-816b-45c225c6df19"
+version = "0.6.9"
+
+[[deps.ImageMetadata]]
+deps = ["AxisArrays", "ImageAxes", "ImageBase", "ImageCore"]
+git-tree-sha1 = "355e2b974f2e3212a75dfb60519de21361ad3cb7"
+uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
+version = "0.9.9"
+
+[[deps.Imath_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "0936ba688c6d201805a83da835b55c61a180db52"
+uuid = "905a6f67-0a94-5f89-b386-d35d92009cd1"
+version = "3.1.11+0"
+
+[[deps.IndirectArrays]]
+git-tree-sha1 = "012e604e1c7458645cb8b436f8fba789a51b257f"
+uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959"
+version = "1.0.0"
+
+[[deps.Inflate]]
+git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d"
+uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
+version = "0.1.5"
+
+[[deps.IntelOpenMP_jll]]
+deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"]
+git-tree-sha1 = "10bd689145d2c3b2a9844005d01087cc1194e79e"
+uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
+version = "2024.2.1+0"
+
+[[deps.InteractiveUtils]]
+deps = ["Markdown"]
+uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
+version = "1.11.0"
+
+[[deps.Interpolations]]
+deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"]
+git-tree-sha1 = "88a101217d7cb38a7b481ccd50d21876e1d1b0e0"
+uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
+version = "0.15.1"
+weakdeps = ["Unitful"]
+
+    [deps.Interpolations.extensions]
+    InterpolationsUnitfulExt = "Unitful"
+
+[[deps.IntervalArithmetic]]
+deps = ["CRlibm_jll", "LinearAlgebra", "MacroTools", "RoundingEmulator"]
+git-tree-sha1 = "24c095b1ec7ee58b936985d31d5df92f9b9cfebb"
+uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
+version = "0.22.19"
+
+    [deps.IntervalArithmetic.extensions]
+    IntervalArithmeticDiffRulesExt = "DiffRules"
+    IntervalArithmeticForwardDiffExt = "ForwardDiff"
+    IntervalArithmeticIntervalSetsExt = "IntervalSets"
+    IntervalArithmeticRecipesBaseExt = "RecipesBase"
+
+    [deps.IntervalArithmetic.weakdeps]
+    DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b"
+    ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
+    IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
+    RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+
+[[deps.IntervalSets]]
+git-tree-sha1 = "dba9ddf07f77f60450fe5d2e2beb9854d9a49bd0"
+uuid = "8197267c-284f-5f27-9208-e0e47529a953"
+version = "0.7.10"
+weakdeps = ["Random", "RecipesBase", "Statistics"]
+
+    [deps.IntervalSets.extensions]
+    IntervalSetsRandomExt = "Random"
+    IntervalSetsRecipesBaseExt = "RecipesBase"
+    IntervalSetsStatisticsExt = "Statistics"
+
+[[deps.InverseFunctions]]
+git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb"
+uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
+version = "0.1.17"
+weakdeps = ["Dates", "Test"]
+
+    [deps.InverseFunctions.extensions]
+    InverseFunctionsDatesExt = "Dates"
+    InverseFunctionsTestExt = "Test"
+
+[[deps.IrrationalConstants]]
+git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2"
+uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
+version = "0.2.2"
+
+[[deps.Isoband]]
+deps = ["isoband_jll"]
+git-tree-sha1 = "f9b6d97355599074dc867318950adaa6f9946137"
+uuid = "f1662d9f-8043-43de-a69a-05efc1cc6ff4"
+version = "0.1.1"
+
+[[deps.IterTools]]
+git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023"
+uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
+version = "1.10.0"
+
+[[deps.IteratorInterfaceExtensions]]
+git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
+uuid = "82899510-4779-5014-852e-03e436cf321d"
+version = "1.0.0"
+
+[[deps.JLLWrappers]]
+deps = ["Artifacts", "Preferences"]
+git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b"
+uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
+version = "1.6.1"
+
+[[deps.JSON]]
+deps = ["Dates", "Mmap", "Parsers", "Unicode"]
+git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a"
+uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
+version = "0.21.4"
+
+[[deps.JSON3]]
+deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"]
+git-tree-sha1 = "1d322381ef7b087548321d3f878cb4c9bd8f8f9b"
+uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
+version = "1.14.1"
+
+    [deps.JSON3.extensions]
+    JSON3ArrowExt = ["ArrowTypes"]
+
+    [deps.JSON3.weakdeps]
+    ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd"
+
+[[deps.JpegTurbo]]
+deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"]
+git-tree-sha1 = "fa6d0bcff8583bac20f1ffa708c3913ca605c611"
+uuid = "b835a17e-a41a-41e7-81f0-2f016b05efe0"
+version = "0.1.5"
+
+[[deps.JpegTurbo_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "25ee0be4d43d0269027024d75a24c24d6c6e590c"
+uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
+version = "3.0.4+0"
+
+[[deps.KernelDensity]]
+deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"]
+git-tree-sha1 = "7d703202e65efa1369de1279c162b915e245eed1"
+uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b"
+version = "0.6.9"
+
+[[deps.LAME_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "170b660facf5df5de098d866564877e119141cbd"
+uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
+version = "3.100.2+0"
+
+[[deps.LERC_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "36bdbc52f13a7d1dcb0f3cd694e01677a515655b"
+uuid = "88015f11-f218-50d7-93a8-a6af411a945d"
+version = "4.0.0+0"
+
+[[deps.LLVMOpenMP_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "78211fb6cbc872f77cad3fc0b6cf647d923f4929"
+uuid = "1d63c593-3942-5779-bab2-d838dc0a180e"
+version = "18.1.7+0"
+
+[[deps.LZO_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "854a9c268c43b77b0a27f22d7fab8d33cdb3a731"
+uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
+version = "2.10.2+1"
+
+[[deps.LaTeXStrings]]
+git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
+uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
+version = "1.4.0"
+
+[[deps.LazyArtifacts]]
+deps = ["Artifacts", "Pkg"]
+uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"
+version = "1.11.0"
+
+[[deps.LazyModules]]
+git-tree-sha1 = "a560dd966b386ac9ae60bdd3a3d3a326062d3c3e"
+uuid = "8cdb02fc-e678-4876-92c5-9defec4f444e"
+version = "0.3.1"
+
+[[deps.LibCURL]]
+deps = ["LibCURL_jll", "MozillaCACerts_jll"]
+uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
+version = "0.6.4"
+
+[[deps.LibCURL_jll]]
+deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
+uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
+version = "8.6.0+0"
+
+[[deps.LibGit2]]
+deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"]
+uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
+version = "1.11.0"
+
+[[deps.LibGit2_jll]]
+deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"]
+uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5"
+version = "1.7.2+0"
+
+[[deps.LibSSH2_jll]]
+deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
+uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
+version = "1.11.0+1"
+
+[[deps.Libdl]]
+uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
+version = "1.11.0"
+
+[[deps.Libffi_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290"
+uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
+version = "3.2.2+1"
+
+[[deps.Libgcrypt_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"]
+git-tree-sha1 = "8be878062e0ffa2c3f67bb58a595375eda5de80b"
+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"
+uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
+version = "1.6.0+0"
+
+[[deps.Libgpg_error_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "c6ce1e19f3aec9b59186bdf06cdf3c4fc5f5f3e6"
+uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
+version = "1.50.0+0"
+
+[[deps.Libiconv_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "61dfdba58e585066d8bce214c5a51eaa0539f269"
+uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
+version = "1.17.0+1"
+
+[[deps.Libmount_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e"
+uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
+version = "2.40.1+0"
+
+[[deps.Libtiff_jll]]
+deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"]
+git-tree-sha1 = "b404131d06f7886402758c9ce2214b636eb4d54a"
+uuid = "89763e89-9b03-5906-acba-b20f662cd828"
+version = "4.7.0+0"
+
+[[deps.Libuuid_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807"
+uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
+version = "2.40.1+0"
+
+[[deps.LinearAlgebra]]
+deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
+uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+version = "1.11.0"
+
+[[deps.LogExpFunctions]]
+deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
+git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea"
+uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
+version = "0.3.28"
+
+    [deps.LogExpFunctions.extensions]
+    LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
+    LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables"
+    LogExpFunctionsInverseFunctionsExt = "InverseFunctions"
+
+    [deps.LogExpFunctions.weakdeps]
+    ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+    ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
+    InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
+
+[[deps.Logging]]
+uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
+version = "1.11.0"
+
+[[deps.LoggingExtras]]
+deps = ["Dates", "Logging"]
+git-tree-sha1 = "f02b56007b064fbfddb4c9cd60161b6dd0f40df3"
+uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36"
+version = "1.1.0"
+
+[[deps.MAT]]
+deps = ["BufferedStreams", "CodecZlib", "HDF5", "SparseArrays"]
+git-tree-sha1 = "1d2dd9b186742b0f317f2530ddcbf00eebb18e96"
+uuid = "23992714-dd62-5051-b70f-ba57cb901cac"
+version = "0.10.7"
+
+[[deps.MKL_jll]]
+deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"]
+git-tree-sha1 = "f046ccd0c6db2832a9f639e2c669c6fe867e5f4f"
+uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
+version = "2024.2.0+0"
+
+[[deps.MPICH_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
+git-tree-sha1 = "7715e65c47ba3941c502bffb7f266a41a7f54423"
+uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4"
+version = "4.2.3+0"
+
+[[deps.MPIPreferences]]
+deps = ["Libdl", "Preferences"]
+git-tree-sha1 = "c105fe467859e7f6e9a852cb15cb4301126fac07"
+uuid = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267"
+version = "0.1.11"
+
+[[deps.MPItrampoline_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
+git-tree-sha1 = "70e830dab5d0775183c99fc75e4c24c614ed7142"
+uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748"
+version = "5.5.1+0"
+
+[[deps.MacroTools]]
+deps = ["Markdown", "Random"]
+git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df"
+uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
+version = "0.5.13"
+
+[[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"
+uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
+version = "0.21.15"
+
+[[deps.MakieCore]]
+deps = ["ColorTypes", "GeometryBasics", "IntervalSets", "Observables"]
+git-tree-sha1 = "4604f03e5b057e8e62a95a44929cafc9585b0fe9"
+uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b"
+version = "0.8.9"
+
+[[deps.MappedArrays]]
+git-tree-sha1 = "2dab0221fe2b0f2cb6754eaa743cc266339f527e"
+uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
+version = "0.4.2"
+
+[[deps.Markdown]]
+deps = ["Base64"]
+uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
+version = "1.11.0"
+
+[[deps.MathTeXEngine]]
+deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"]
+git-tree-sha1 = "f45c8916e8385976e1ccd055c9874560c257ab13"
+uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
+version = "0.6.2"
+
+[[deps.MbedTLS]]
+deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"]
+git-tree-sha1 = "c067a280ddc25f196b5e7df3877c6b226d390aaf"
+uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
+version = "1.1.9"
+
+[[deps.MbedTLS_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
+version = "2.28.6+0"
+
+[[deps.Meshes]]
+deps = ["Bessels", "CircularArrays", "Distances", "IterTools", "LinearAlgebra", "NearestNeighbors", "Random", "Rotations", "SparseArrays", "StaticArrays", "StatsBase", "Tables", "TransformsBase"]
+git-tree-sha1 = "a1a152787767f3393362276452897605584964b1"
+uuid = "eacbb407-ea5a-433e-ab97-5258b1ca43fa"
+version = "0.29.0"
+
+[[deps.MicrosoftMPI_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "f12a29c4400ba812841c6ace3f4efbb6dbb3ba01"
+uuid = "9237b28f-5490-5468-be7b-bb81f5f5e6cf"
+version = "10.1.4+2"
+
+[[deps.Missings]]
+deps = ["DataAPI"]
+git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d"
+uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
+version = "1.2.0"
+
+[[deps.Mmap]]
+uuid = "a63ad114-7e13-5084-954f-fe012c677804"
+version = "1.11.0"
+
+[[deps.MosaicViews]]
+deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"]
+git-tree-sha1 = "7b86a5d4d70a9f5cdf2dacb3cbe6d251d1a61dbe"
+uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389"
+version = "0.3.4"
+
+[[deps.MozillaCACerts_jll]]
+uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
+version = "2023.12.12"
+
+[[deps.MsgPack]]
+deps = ["Serialization"]
+git-tree-sha1 = "f5db02ae992c260e4826fe78c942954b48e1d9c2"
+uuid = "99f44e22-a591-53d1-9472-aa23ef4bd671"
+version = "1.2.1"
+
+[[deps.NearestNeighbors]]
+deps = ["Distances", "StaticArrays"]
+git-tree-sha1 = "3cebfc94a0754cc329ebc3bab1e6c89621e791ad"
+uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
+version = "0.4.20"
+
+[[deps.Netpbm]]
+deps = ["FileIO", "ImageCore", "ImageMetadata"]
+git-tree-sha1 = "d92b107dbb887293622df7697a2223f9f8176fcd"
+uuid = "f09324ee-3d7c-5217-9330-fc30815ba969"
+version = "1.1.1"
+
+[[deps.NetworkOptions]]
+uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
+version = "1.2.0"
+
+[[deps.NyxWidgets]]
+deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
+git-tree-sha1 = "936f80aa61413c47da00f96abbc0186078698bca"
+repo-rev = "main"
+repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
+uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
+version = "0.1.1"
+
+[[deps.Observables]]
+git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
+uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
+version = "0.5.5"
+
+[[deps.ObservationPolicies]]
+deps = ["Observables"]
+git-tree-sha1 = "d54b4c26b41238806c172f85e1913736c2583b0a"
+repo-rev = "main"
+repo-url = "https://gitlab.com/dbc-nyx/ObservationPolicies.jl"
+uuid = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
+version = "0.2.4"
+
+[[deps.OffsetArrays]]
+git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e"
+uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
+version = "1.14.1"
+weakdeps = ["Adapt"]
+
+    [deps.OffsetArrays.extensions]
+    OffsetArraysAdaptExt = "Adapt"
+
+[[deps.Ogg_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "887579a3eb005446d514ab7aeac5d1d027658b8f"
+uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
+version = "1.3.5+1"
+
+[[deps.OpenBLAS_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
+uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
+version = "0.3.27+1"
+
+[[deps.OpenEXR]]
+deps = ["Colors", "FileIO", "OpenEXR_jll"]
+git-tree-sha1 = "327f53360fdb54df7ecd01e96ef1983536d1e633"
+uuid = "52e1d378-f018-4a11-a4be-720524705ac7"
+version = "0.3.2"
+
+[[deps.OpenEXR_jll]]
+deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "8292dd5c8a38257111ada2174000a33745b06d4e"
+uuid = "18a262bb-aa17-5467-a713-aee519bc75cb"
+version = "3.2.4+0"
+
+[[deps.OpenLibm_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
+version = "0.8.1+2"
+
+[[deps.OpenMPI_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML", "Zlib_jll"]
+git-tree-sha1 = "bfce6d523861a6c562721b262c0d1aaeead2647f"
+uuid = "fe0851c0-eecd-5654-98d4-656369965a5c"
+version = "5.0.5+0"
+
+[[deps.OpenSSL]]
+deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
+git-tree-sha1 = "38cb508d080d21dc1128f7fb04f20387ed4c0af4"
+uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
+version = "1.4.3"
+
+[[deps.OpenSSL_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10"
+uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
+version = "3.0.15+1"
+
+[[deps.OpenSpecFun_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
+uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
+version = "0.5.5+0"
+
+[[deps.Opus_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "6703a85cb3781bd5909d48730a67205f3f31a575"
+uuid = "91d4177d-7536-5919-b921-800302f37372"
+version = "1.3.3+0"
+
+[[deps.OrderedCollections]]
+git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5"
+uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
+version = "1.6.3"
+
+[[deps.PCRE2_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15"
+version = "10.42.0+1"
+
+[[deps.PDMats]]
+deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
+git-tree-sha1 = "949347156c25054de2db3b166c52ac4728cbad65"
+uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
+version = "0.11.31"
+
+[[deps.PNGFiles]]
+deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"]
+git-tree-sha1 = "67186a2bc9a90f9f85ff3cc8277868961fb57cbd"
+uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
+version = "0.4.3"
+
+[[deps.Packing]]
+deps = ["GeometryBasics"]
+git-tree-sha1 = "ec3edfe723df33528e085e632414499f26650501"
+uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566"
+version = "0.5.0"
+
+[[deps.PaddedViews]]
+deps = ["OffsetArrays"]
+git-tree-sha1 = "0fac6313486baae819364c52b4f483450a9d793f"
+uuid = "5432bcbf-9aad-5242-b902-cca2824c8663"
+version = "0.5.12"
+
+[[deps.Parsers]]
+deps = ["Dates", "PrecompileTools", "UUIDs"]
+git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821"
+uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
+version = "2.8.1"
+
+[[deps.Pixman_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"]
+git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b"
+uuid = "30392449-352a-5448-841d-b1acce4e97dc"
+version = "0.43.4+0"
+
+[[deps.Pkg]]
+deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"]
+uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
+version = "1.11.0"
+weakdeps = ["REPL"]
+
+    [deps.Pkg.extensions]
+    REPLExt = "REPL"
+
+[[deps.PkgVersion]]
+deps = ["Pkg"]
+git-tree-sha1 = "f9501cc0430a26bc3d156ae1b5b0c1b47af4d6da"
+uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
+version = "0.3.3"
+
+[[deps.PlanarLarvae]]
+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"
+uuid = "c2615984-ef14-4d40-b148-916c85b43307"
+version = "0.16.0"
+
+[[deps.PlotUtils]]
+deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "StableRNGs", "Statistics"]
+git-tree-sha1 = "3ca9a356cd2e113c420f2c13bea19f8d3fb1cb18"
+uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
+version = "1.4.3"
+
+[[deps.PolygonOps]]
+git-tree-sha1 = "77b3d3605fc1cd0b42d95eba87dfcd2bf67d5ff6"
+uuid = "647866c9-e3ac-4575-94e7-e3d426903924"
+version = "0.1.2"
+
+[[deps.PrecompileTools]]
+deps = ["Preferences"]
+git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f"
+uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
+version = "1.2.1"
+
+[[deps.Preferences]]
+deps = ["TOML"]
+git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6"
+uuid = "21216c6a-2e73-6563-6e65-726566657250"
+version = "1.4.3"
+
+[[deps.Printf]]
+deps = ["Unicode"]
+uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
+version = "1.11.0"
+
+[[deps.ProgressMeter]]
+deps = ["Distributed", "Printf"]
+git-tree-sha1 = "8f6bc219586aef8baf0ff9a5fe16ee9c70cb65e4"
+uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
+version = "1.10.2"
+
+[[deps.PtrArrays]]
+git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f"
+uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
+version = "1.2.1"
+
+[[deps.QOI]]
+deps = ["ColorTypes", "FileIO", "FixedPointNumbers"]
+git-tree-sha1 = "18e8f4d1426e965c7b532ddd260599e1510d26ce"
+uuid = "4b34888f-f399-49d4-9bb3-47ed5cae4e65"
+version = "1.0.0"
+
+[[deps.QuadGK]]
+deps = ["DataStructures", "LinearAlgebra"]
+git-tree-sha1 = "cda3b045cf9ef07a08ad46731f5a3165e56cf3da"
+uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
+version = "2.11.1"
+
+    [deps.QuadGK.extensions]
+    QuadGKEnzymeExt = "Enzyme"
+
+    [deps.QuadGK.weakdeps]
+    Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
+
+[[deps.Quaternions]]
+deps = ["LinearAlgebra", "Random", "RealDot"]
+git-tree-sha1 = "994cc27cdacca10e68feb291673ec3a76aa2fae9"
+uuid = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0"
+version = "0.7.6"
+
+[[deps.REPL]]
+deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"]
+uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
+version = "1.11.0"
+
+[[deps.Random]]
+deps = ["SHA"]
+uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+version = "1.11.0"
+
+[[deps.RangeArrays]]
+git-tree-sha1 = "b9039e93773ddcfc828f12aadf7115b4b4d225f5"
+uuid = "b3c3ace0-ae52-54e7-9d0b-2c1406fd6b9d"
+version = "0.3.2"
+
+[[deps.Ratios]]
+deps = ["Requires"]
+git-tree-sha1 = "1342a47bf3260ee108163042310d26f2be5ec90b"
+uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
+version = "0.4.5"
+weakdeps = ["FixedPointNumbers"]
+
+    [deps.Ratios.extensions]
+    RatiosFixedPointNumbersExt = "FixedPointNumbers"
+
+[[deps.RealDot]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "9f0a1b71baaf7650f4fa8a1d168c7fb6ee41f0c9"
+uuid = "c1ae055f-0cd5-4b69-90a6-9a35b1a98df9"
+version = "0.1.0"
+
+[[deps.RecipesBase]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff"
+uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+version = "1.3.4"
+
+[[deps.Reexport]]
+git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
+uuid = "189a3867-3050-52da-a836-e630ba90ab69"
+version = "1.2.2"
+
+[[deps.RelocatableFolders]]
+deps = ["SHA", "Scratch"]
+git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864"
+uuid = "05181044-ff0b-4ac5-8273-598c1e38db00"
+version = "1.0.1"
+
+[[deps.Requires]]
+deps = ["UUIDs"]
+git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
+uuid = "ae029012-a4dd-5104-9daa-d747884805df"
+version = "1.3.0"
+
+[[deps.Rmath]]
+deps = ["Random", "Rmath_jll"]
+git-tree-sha1 = "852bd0f55565a9e973fcfee83a84413270224dc4"
+uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
+version = "0.8.0"
+
+[[deps.Rmath_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8"
+uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
+version = "0.5.1+0"
+
+[[deps.Rotations]]
+deps = ["LinearAlgebra", "Quaternions", "Random", "StaticArrays"]
+git-tree-sha1 = "5680a9276685d392c87407df00d57c9924d9f11e"
+uuid = "6038ab10-8711-5258-84ad-4b1120ba62dc"
+version = "1.7.1"
+weakdeps = ["RecipesBase"]
+
+    [deps.Rotations.extensions]
+    RotationsRecipesBaseExt = "RecipesBase"
+
+[[deps.RoundingEmulator]]
+git-tree-sha1 = "40b9edad2e5287e05bd413a38f61a8ff55b9557b"
+uuid = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705"
+version = "0.2.1"
+
+[[deps.SHA]]
+uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
+version = "0.7.0"
+
+[[deps.SIMD]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "98ca7c29edd6fc79cd74c61accb7010a4e7aee33"
+uuid = "fdea26ae-647d-5447-a871-4b548cad5224"
+version = "3.6.0"
+
+[[deps.Scratch]]
+deps = ["Dates"]
+git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386"
+uuid = "6c6a2e73-6563-6170-7368-637461726353"
+version = "1.2.1"
+
+[[deps.Serialization]]
+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"
+uuid = "65257c39-d410-5151-9873-9b3e5be5013e"
+version = "0.4.1"
+
+[[deps.SharedArrays]]
+deps = ["Distributed", "Mmap", "Random", "Serialization"]
+uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
+version = "1.11.0"
+
+[[deps.Showoff]]
+deps = ["Dates", "Grisu"]
+git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
+uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
+version = "1.0.3"
+
+[[deps.SignedDistanceFields]]
+deps = ["Random", "Statistics", "Test"]
+git-tree-sha1 = "d263a08ec505853a5ff1c1ebde2070419e3f28e9"
+uuid = "73760f76-fbc4-59ce-8f25-708e95d2df96"
+version = "0.4.0"
+
+[[deps.SimpleBufferStream]]
+git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1"
+uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7"
+version = "1.2.0"
+
+[[deps.SimpleTraits]]
+deps = ["InteractiveUtils", "MacroTools"]
+git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
+uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
+version = "0.9.4"
+
+[[deps.Sixel]]
+deps = ["Dates", "FileIO", "ImageCore", "IndirectArrays", "OffsetArrays", "REPL", "libsixel_jll"]
+git-tree-sha1 = "2da10356e31327c7096832eb9cd86307a50b1eb6"
+uuid = "45858cf5-a6b0-47a3-bbea-62219f50df47"
+version = "0.1.3"
+
+[[deps.Sockets]]
+uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
+version = "1.11.0"
+
+[[deps.SortingAlgorithms]]
+deps = ["DataStructures"]
+git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085"
+uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
+version = "1.2.1"
+
+[[deps.SparseArrays]]
+deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"]
+uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
+version = "1.11.0"
+
+[[deps.SpecialFunctions]]
+deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
+git-tree-sha1 = "2f5d4697f21388cbe1ff299430dd169ef97d7e14"
+uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
+version = "2.4.0"
+weakdeps = ["ChainRulesCore"]
+
+    [deps.SpecialFunctions.extensions]
+    SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
+
+[[deps.StableRNGs]]
+deps = ["Random"]
+git-tree-sha1 = "83e6cce8324d49dfaf9ef059227f91ed4441a8e5"
+uuid = "860ef19b-820b-49d6-a774-d7a799459cd3"
+version = "1.0.2"
+
+[[deps.StackViews]]
+deps = ["OffsetArrays"]
+git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c"
+uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15"
+version = "0.1.1"
+
+[[deps.StaticArrays]]
+deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"]
+git-tree-sha1 = "777657803913ffc7e8cc20f0fd04b634f871af8f"
+uuid = "90137ffa-7385-5640-81b9-e52037218182"
+version = "1.9.8"
+weakdeps = ["ChainRulesCore", "Statistics"]
+
+    [deps.StaticArrays.extensions]
+    StaticArraysChainRulesCoreExt = "ChainRulesCore"
+    StaticArraysStatisticsExt = "Statistics"
+
+[[deps.StaticArraysCore]]
+git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682"
+uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
+version = "1.4.3"
+
+[[deps.Statistics]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0"
+uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+version = "1.11.1"
+weakdeps = ["SparseArrays"]
+
+    [deps.Statistics.extensions]
+    SparseArraysExt = ["SparseArrays"]
+
+[[deps.StatsAPI]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed"
+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"
+uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
+version = "0.34.3"
+
+[[deps.StatsFuns]]
+deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
+git-tree-sha1 = "b423576adc27097764a90e163157bcfc9acf0f46"
+uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
+version = "1.3.2"
+weakdeps = ["ChainRulesCore", "InverseFunctions"]
+
+    [deps.StatsFuns.extensions]
+    StatsFunsChainRulesCoreExt = "ChainRulesCore"
+    StatsFunsInverseFunctionsExt = "InverseFunctions"
+
+[[deps.StructArrays]]
+deps = ["ConstructionBase", "DataAPI", "Tables"]
+git-tree-sha1 = "f4dc295e983502292c4c3f951dbb4e985e35b3be"
+uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
+version = "0.6.18"
+
+    [deps.StructArrays.extensions]
+    StructArraysAdaptExt = "Adapt"
+    StructArraysGPUArraysCoreExt = "GPUArraysCore"
+    StructArraysSparseArraysExt = "SparseArrays"
+    StructArraysStaticArraysExt = "StaticArrays"
+
+    [deps.StructArrays.weakdeps]
+    Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
+    GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527"
+    SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
+    StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
+
+[[deps.StructTypes]]
+deps = ["Dates", "UUIDs"]
+git-tree-sha1 = "159331b30e94d7b11379037feeb9b690950cace8"
+uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
+version = "1.11.0"
+
+[[deps.StyledStrings]]
+uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
+version = "1.11.0"
+
+[[deps.SuiteSparse]]
+deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
+uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"
+
+[[deps.SuiteSparse_jll]]
+deps = ["Artifacts", "Libdl", "libblastrampoline_jll"]
+uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
+version = "7.7.0+0"
+
+[[deps.TOML]]
+deps = ["Dates"]
+uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
+version = "1.0.3"
+
+[[deps.TableTraits]]
+deps = ["IteratorInterfaceExtensions"]
+git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
+uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
+version = "1.0.1"
+
+[[deps.Tables]]
+deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
+git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297"
+uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
+version = "1.12.0"
+
+[[deps.Tar]]
+deps = ["ArgTools", "SHA"]
+uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
+version = "1.10.0"
+
+[[deps.TensorCore]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6"
+uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50"
+version = "0.1.1"
+
+[[deps.Test]]
+deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
+uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+version = "1.11.0"
+
+[[deps.ThreadPools]]
+deps = ["Printf", "RecipesBase", "Statistics"]
+git-tree-sha1 = "50cb5f85d5646bc1422aa0238aa5bfca99ca9ae7"
+uuid = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431"
+version = "2.1.1"
+
+[[deps.TidyObservables]]
+deps = ["Observables"]
+git-tree-sha1 = "0589ec7397374678942cae9aa356b4bb6c1e9bf4"
+repo-rev = "main"
+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"
+uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
+version = "0.11.0"
+
+[[deps.TranscodingStreams]]
+git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742"
+uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
+version = "0.11.3"
+
+[[deps.TransformsBase]]
+deps = ["AbstractTrees"]
+git-tree-sha1 = "2412fb54902b0063c69c2bcfbec6b571120cc856"
+uuid = "28dd2a49-a57a-4bfb-84ca-1a49db9b96b8"
+version = "0.1.2"
+
+[[deps.TriplotBase]]
+git-tree-sha1 = "4d4ed7f294cda19382ff7de4c137d24d16adc89b"
+uuid = "981d1d27-644d-49a2-9326-4793e63143c3"
+version = "0.1.0"
+
+[[deps.URIs]]
+git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
+uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
+version = "1.5.1"
+
+[[deps.UUIDs]]
+deps = ["Random", "SHA"]
+uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
+version = "1.11.0"
+
+[[deps.Unicode]]
+uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
+version = "1.11.0"
+
+[[deps.UnicodeFun]]
+deps = ["REPL"]
+git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf"
+uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1"
+version = "0.4.1"
+
+[[deps.Unitful]]
+deps = ["Dates", "LinearAlgebra", "Random"]
+git-tree-sha1 = "d95fe458f26209c66a187b1114df96fd70839efd"
+uuid = "1986cc42-f94f-5a68-af5c-568840ba703d"
+version = "1.21.0"
+weakdeps = ["ConstructionBase", "InverseFunctions"]
+
+    [deps.Unitful.extensions]
+    ConstructionBaseUnitfulExt = "ConstructionBase"
+    InverseFunctionsUnitfulExt = "InverseFunctions"
+
+[[deps.WGLMakie]]
+deps = ["Bonito", "Colors", "FileIO", "FreeTypeAbstraction", "GeometryBasics", "Hyperscript", "LinearAlgebra", "Makie", "Observables", "PNGFiles", "PrecompileTools", "RelocatableFolders", "ShaderAbstractions", "StaticArrays"]
+git-tree-sha1 = "13cab94d885d7760d487d7e2f30c2d6df4643880"
+uuid = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"
+version = "0.10.15"
+
+[[deps.WebP]]
+deps = ["CEnum", "ColorTypes", "FileIO", "FixedPointNumbers", "ImageCore", "libwebp_jll"]
+git-tree-sha1 = "f1f6d497ff84039deeb37f264396dac0c2250497"
+uuid = "e3aaa7dc-3e4b-44e0-be63-ffb868ccd7c1"
+version = "0.1.2"
+
+[[deps.WidgetsBase]]
+deps = ["Observables"]
+git-tree-sha1 = "30a1d631eb06e8c868c559599f915a62d55c2601"
+uuid = "eead4739-05f7-45a1-878c-cee36b57321c"
+version = "0.1.4"
+
+[[deps.WoodburyMatrices]]
+deps = ["LinearAlgebra", "SparseArrays"]
+git-tree-sha1 = "c1a7aa6219628fcd757dede0ca95e245c5cd9511"
+uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6"
+version = "1.0.0"
+
+[[deps.XML2_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"]
+git-tree-sha1 = "6a451c6f33a176150f315726eba8b92fbfdb9ae7"
+uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
+version = "2.13.4+0"
+
+[[deps.XSLT_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "XML2_jll", "Zlib_jll"]
+git-tree-sha1 = "a54ee957f4c86b526460a720dbc882fa5edcbefc"
+uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
+version = "1.1.41+0"
+
+[[deps.XZ_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "15e637a697345f6743674f1322beefbc5dcd5cfc"
+uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800"
+version = "5.6.3+0"
+
+[[deps.Xorg_libX11_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
+git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495"
+uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
+version = "1.8.6+0"
+
+[[deps.Xorg_libXau_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8"
+uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
+version = "1.0.11+0"
+
+[[deps.Xorg_libXdmcp_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7"
+uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
+version = "1.1.4+0"
+
+[[deps.Xorg_libXext_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"]
+git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85"
+uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
+version = "1.3.6+0"
+
+[[deps.Xorg_libXrender_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"]
+git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe"
+uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
+version = "0.9.11+0"
+
+[[deps.Xorg_libpthread_stubs_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9"
+uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
+version = "0.1.1+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"
+uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
+version = "1.17.0+0"
+
+[[deps.Xorg_xtrans_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77"
+uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
+version = "1.5.0+0"
+
+[[deps.Zlib_jll]]
+deps = ["Libdl"]
+uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
+version = "1.2.13+1"
+
+[[deps.Zstd_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b"
+uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
+version = "1.5.6+1"
+
+[[deps.isoband_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "51b5eeb3f98367157a7a12a1fb0aa5328946c03c"
+uuid = "9a68df92-36a6-505f-a73e-abb412b6bfb4"
+version = "0.2.3+0"
+
+[[deps.libaec_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "46bf7be2917b59b761247be3f317ddf75e50e997"
+uuid = "477f73a3-ac25-53e9-8cc3-50b2fa2566f0"
+version = "1.1.2+0"
+
+[[deps.libaom_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1827acba325fdcdf1d2647fc8d5301dd9ba43a9d"
+uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b"
+version = "3.9.0+0"
+
+[[deps.libass_jll]]
+deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "e17c115d55c5fbb7e52ebedb427a0dca79d4484e"
+uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
+version = "0.15.2+0"
+
+[[deps.libblastrampoline_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
+version = "5.11.0+0"
+
+[[deps.libfdk_aac_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "8a22cf860a7d27e4f3498a0fe0811a7957badb38"
+uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
+version = "2.0.3+0"
+
+[[deps.libpng_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "b70c870239dc3d7bc094eb2d6be9b73d27bef280"
+uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
+version = "1.6.44+0"
+
+[[deps.libsixel_jll]]
+deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "libpng_jll"]
+git-tree-sha1 = "7dfa0fd9c783d3d0cc43ea1af53d69ba45c447df"
+uuid = "075b6546-f08a-558a-be8f-8157d0f608a5"
+version = "1.10.3+1"
+
+[[deps.libvorbis_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
+git-tree-sha1 = "490376214c4721cdaca654041f635213c6165cb3"
+uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
+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"
+uuid = "c5f90fcd-3b7e-5836-afba-fc50a0988cb2"
+version = "1.4.0+0"
+
+[[deps.nghttp2_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
+version = "1.59.0+0"
+
+[[deps.oneTBB_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "7d0ea0f4895ef2f5cb83645fa689e52cb55cf493"
+uuid = "1317d2d5-d96f-522e-a858-c73665f53c3e"
+version = "2021.12.0+0"
+
+[[deps.p7zip_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
+version = "17.4.0+2"
+
+[[deps.x264_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "35976a1216d6c066ea32cba2150c4fa682b276fc"
+uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
+version = "10164.0.0+0"
+
+[[deps.x265_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "dcc541bb19ed5b0ede95581fb2e41ecf179527d2"
+uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
+version = "3.6.0+0"
diff --git a/Manifest.toml b/Manifest.toml
index 46f7dea..d02ed0c 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.3"
+julia_version = "1.10.6"
 manifest_format = "2.0"
-project_hash = "cb952709b0f0743fc5b93ca3d08c93ff9353644c"
+project_hash = "0fb14e688a33d3d8ba0bbce1542d1ada113967c7"
 
 [[deps.AbstractFFTs]]
 deps = ["LinearAlgebra"]
@@ -22,14 +22,19 @@ version = "0.4.5"
 
 [[deps.Adapt]]
 deps = ["LinearAlgebra", "Requires"]
-git-tree-sha1 = "6a55b747d1812e699320963ffde36f1ebdda4099"
+git-tree-sha1 = "50c3c56a52972d78e8be9fd135bfb91c9574c140"
 uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
-version = "4.0.4"
+version = "4.1.1"
 weakdeps = ["StaticArrays"]
 
     [deps.Adapt.extensions]
     AdaptStaticArraysExt = "StaticArrays"
 
+[[deps.AdaptivePredicates]]
+git-tree-sha1 = "7e651ea8d262d2d74ce75fdf47c4d63c07dba7a6"
+uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7"
+version = "1.2.0"
+
 [[deps.AliasTables]]
 deps = ["PtrArrays", "Random"]
 git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff"
@@ -50,10 +55,10 @@ version = "1.1.1"
 uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
 
 [[deps.Automa]]
-deps = ["TranscodingStreams"]
-git-tree-sha1 = "ef9997b3d5547c48b41c7bd8899e812a917b409d"
+deps = ["PrecompileTools", "SIMD", "TranscodingStreams"]
+git-tree-sha1 = "a8f503e8e1a5f583fbef15a8440c8c7e32185df2"
 uuid = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b"
-version = "0.8.4"
+version = "1.1.0"
 
 [[deps.AxisAlgorithms]]
 deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
@@ -75,39 +80,53 @@ git-tree-sha1 = "4435559dc39793d53a9e3d278e185e920b4619ef"
 uuid = "0e736298-9ec6-45e8-9647-e4fc86a2fe38"
 version = "0.2.8"
 
+[[deps.BitFlags]]
+git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d"
+uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35"
+version = "0.1.9"
+
+[[deps.Bonito]]
+deps = ["Base64", "CodecZlib", "Colors", "Dates", "Deno_jll", "HTTP", "Hyperscript", "LinearAlgebra", "Markdown", "MsgPack", "Observables", "RelocatableFolders", "SHA", "Sockets", "Tables", "ThreadPools", "URIs", "UUIDs", "WidgetsBase"]
+git-tree-sha1 = "d7635780a8cfe0cb43c075276fd358c5b166695e"
+uuid = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
+version = "3.2.4"
+
 [[deps.BufferedStreams]]
-git-tree-sha1 = "4ae47f9a4b1dc19897d3743ff13685925c5202ec"
+git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643"
 uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
-version = "1.2.1"
+version = "1.2.2"
 
 [[deps.Bzip2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd"
+git-tree-sha1 = "8873e196c2eb87962a2048b3b8e08946535864a1"
 uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
-version = "1.0.8+1"
+version = "1.0.8+2"
 
 [[deps.CEnum]]
 git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
 uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
 version = "0.5.0"
 
+[[deps.CRC32c]]
+uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc"
+
+[[deps.CRlibm_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc"
+uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0"
+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 = "a2f1c8c668c8e3cb4cca4e57a8efdb09067bb3fd"
+git-tree-sha1 = "009060c9a6168704143100f36ab08f06c2af4642"
 uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
-version = "1.18.0+2"
-
-[[deps.Calculus]]
-deps = ["LinearAlgebra"]
-git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad"
-uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9"
-version = "0.5.1"
+version = "1.18.2+1"
 
 [[deps.ChainRulesCore]]
 deps = ["Compat", "LinearAlgebra"]
-git-tree-sha1 = "575cd02e080939a33b6df6c5853d14924c08e35b"
+git-tree-sha1 = "3e4b134270b372f2ed4d4d0e936aabaefc1802bc"
 uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
-version = "1.23.0"
+version = "1.25.0"
 weakdeps = ["SparseArrays"]
 
     [deps.ChainRulesCore.extensions]
@@ -121,9 +140,9 @@ version = "1.4.0"
 
 [[deps.CodecZlib]]
 deps = ["TranscodingStreams", "Zlib_jll"]
-git-tree-sha1 = "59939d8a997469ee05c4b4944560a820f9ba0d73"
+git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759"
 uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
-version = "0.7.4"
+version = "0.7.6"
 
 [[deps.ColorBrewer]]
 deps = ["Colors", "JSON", "Test"]
@@ -133,9 +152,9 @@ version = "0.4.0"
 
 [[deps.ColorSchemes]]
 deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"]
-git-tree-sha1 = "4b270d6465eb21ae89b732182c20dc165f8bf9f2"
+git-tree-sha1 = "c785dfb1b3bfddd1da557e861b919819b82bbe5b"
 uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
-version = "3.25.0"
+version = "3.27.1"
 
 [[deps.ColorTypes]]
 deps = ["FixedPointNumbers", "Random"]
@@ -144,10 +163,14 @@ uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
 version = "0.11.5"
 
 [[deps.ColorVectorSpace]]
-deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "SpecialFunctions", "Statistics", "TensorCore"]
-git-tree-sha1 = "600cc5508d66b78aae350f7accdb58763ac18589"
+deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"]
+git-tree-sha1 = "a1f44953f2382ebb937d60dafbe2deea4bd23249"
 uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
-version = "0.9.10"
+version = "0.10.0"
+weakdeps = ["SpecialFunctions"]
+
+    [deps.ColorVectorSpace.extensions]
+    SpecialFunctionsExt = "SpecialFunctions"
 
 [[deps.Colors]]
 deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
@@ -157,9 +180,9 @@ version = "0.12.11"
 
 [[deps.Compat]]
 deps = ["TOML", "UUIDs"]
-git-tree-sha1 = "b1c55339b7c6c350ee89f2c1604299660525b248"
+git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215"
 uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
-version = "4.15.0"
+version = "4.16.0"
 weakdeps = ["Dates", "LinearAlgebra"]
 
     [deps.Compat.extensions]
@@ -170,15 +193,21 @@ deps = ["Artifacts", "Libdl"]
 uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
 version = "1.1.1+0"
 
+[[deps.ConcurrentUtilities]]
+deps = ["Serialization", "Sockets"]
+git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1"
+uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
+version = "2.4.2"
+
 [[deps.ConstructionBase]]
-deps = ["LinearAlgebra"]
-git-tree-sha1 = "260fd2400ed2dab602a7c15cf10c1933c59930a2"
+git-tree-sha1 = "76219f1ed5771adbb096743bff43fb5fdd4c1157"
 uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
-version = "1.5.5"
-weakdeps = ["IntervalSets", "StaticArrays"]
+version = "1.5.8"
+weakdeps = ["IntervalSets", "LinearAlgebra", "StaticArrays"]
 
     [deps.ConstructionBase.extensions]
     ConstructionBaseIntervalSetsExt = "IntervalSets"
+    ConstructionBaseLinearAlgebraExt = "LinearAlgebra"
     ConstructionBaseStaticArraysExt = "StaticArrays"
 
 [[deps.Contour]]
@@ -206,17 +235,29 @@ version = "1.0.0"
 deps = ["Printf"]
 uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
 
+[[deps.DelaunayTriangulation]]
+deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "PrecompileTools", "Random"]
+git-tree-sha1 = "89df54fbe66e5872d91d8c2cd3a375f660c3fd64"
+uuid = "927a84f5-c5f4-47a5-9785-b46e178433df"
+version = "1.6.1"
+
 [[deps.DelimitedFiles]]
 deps = ["Mmap"]
 git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae"
 uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
 version = "1.9.1"
 
+[[deps.Deno_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "cd6756e833c377e0ce9cd63fb97689a255f12323"
+uuid = "04572ae6-984a-583e-9378-9577a1c2574d"
+version = "1.33.4+0"
+
 [[deps.Distances]]
 deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
-git-tree-sha1 = "66c4c81f259586e8f002eacebc177e1fb06363b0"
+git-tree-sha1 = "c7e3a542b999843086e2f29dac96a618c105be1d"
 uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
-version = "0.10.11"
+version = "0.10.12"
 weakdeps = ["ChainRulesCore", "SparseArrays"]
 
     [deps.Distances.extensions]
@@ -229,9 +270,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 = "22c595ca4146c07b16bcf9c8bea86f731f7109d2"
+git-tree-sha1 = "3101c32aab536e7a27b1763c0797dba151b899ad"
 uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
-version = "0.25.108"
+version = "0.25.113"
 
     [deps.Distributions.extensions]
     DistributionsChainRulesCoreExt = "ChainRulesCore"
@@ -260,18 +301,29 @@ deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
 uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
 version = "1.6.0"
 
-[[deps.DualNumbers]]
-deps = ["Calculus", "NaNMath", "SpecialFunctions"]
-git-tree-sha1 = "5837a837389fccf076445fce071c8ddaea35a566"
-uuid = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
-version = "0.6.8"
-
 [[deps.EarCut_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
 git-tree-sha1 = "e3290f2d49e661fbd94046d7e3726ffcb2d41053"
 uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
 version = "2.2.4+0"
 
+[[deps.EnumX]]
+git-tree-sha1 = "bdb1942cd4c45e3c678fd11569d5cccd80976237"
+uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
+version = "1.0.4"
+
+[[deps.ExactPredicates]]
+deps = ["IntervalArithmetic", "Random", "StaticArrays"]
+git-tree-sha1 = "b3f2ff58735b5f024c392fde763f29b057e4b025"
+uuid = "429591f6-91af-11e9-00e2-59fbe8cec110"
+version = "2.2.8"
+
+[[deps.ExceptionUnwrapping]]
+deps = ["Test"]
+git-tree-sha1 = "dcb08a0d93ec0b1cdc4af184b26b591e9695423a"
+uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4"
+version = "0.1.10"
+
 [[deps.Expat_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
 git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7"
@@ -279,21 +331,15 @@ uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
 version = "2.6.2+0"
 
 [[deps.Extents]]
-git-tree-sha1 = "2140cd04483da90b2da7f99b2add0750504fc39c"
+git-tree-sha1 = "81023caa0021a41712685887db1fc03db26f41f5"
 uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
-version = "0.1.2"
-
-[[deps.FFMPEG]]
-deps = ["FFMPEG_jll"]
-git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
-uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
-version = "0.4.1"
+version = "0.1.4"
 
 [[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"]
-git-tree-sha1 = "466d45dc38e15794ec7d5d63ec03d776a9aff36e"
+git-tree-sha1 = "8cc47f299902e13f90405ddb5bf87e5d474c0d38"
 uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
-version = "4.4.4+1"
+version = "6.1.2+0"
 
 [[deps.FFTW]]
 deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
@@ -303,24 +349,41 @@ version = "1.8.0"
 
 [[deps.FFTW_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "c6033cc3892d0ef5bb9cd29b7f2f0331ea5184ea"
+git-tree-sha1 = "4d81ed14783ec49ce9f2e168208a12ce1815aa25"
 uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
-version = "3.3.10+0"
+version = "3.3.10+1"
 
 [[deps.FileIO]]
 deps = ["Pkg", "Requires", "UUIDs"]
-git-tree-sha1 = "82d8afa92ecf4b52d78d869f038ebfb881267322"
+git-tree-sha1 = "91e0e5c68d02bcdaae76d3c8ceb4361e8f28d2e9"
 uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
-version = "1.16.3"
+version = "1.16.5"
+
+[[deps.FilePaths]]
+deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"]
+git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629"
+uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824"
+version = "0.8.3"
+
+[[deps.FilePathsBase]]
+deps = ["Compat", "Dates"]
+git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361"
+uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
+version = "0.9.22"
+weakdeps = ["Mmap", "Test"]
+
+    [deps.FilePathsBase.extensions]
+    FilePathsBaseMmapExt = "Mmap"
+    FilePathsBaseTestExt = "Test"
 
 [[deps.FileWatching]]
 uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
 
 [[deps.FillArrays]]
 deps = ["LinearAlgebra"]
-git-tree-sha1 = "0653c0a2396a6da5bc4766c43041ef5fd3efbe57"
+git-tree-sha1 = "6a70198746448456524cb442b8af316927ff3e1a"
 uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
-version = "1.11.0"
+version = "1.13.0"
 weakdeps = ["PDMats", "SparseArrays", "Statistics"]
 
     [deps.FillArrays.extensions]
@@ -345,12 +408,6 @@ git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc"
 uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
 version = "1.3.7"
 
-[[deps.Formatting]]
-deps = ["Logging", "Printf"]
-git-tree-sha1 = "fb409abab2caf118986fc597ba84b50cbaf00b87"
-uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
-version = "0.4.3"
-
 [[deps.FreeType]]
 deps = ["CEnum", "FreeType2_jll"]
 git-tree-sha1 = "907369da0f8e80728ab49c1c7e09327bf0d6d999"
@@ -365,9 +422,9 @@ version = "2.13.2+0"
 
 [[deps.FreeTypeAbstraction]]
 deps = ["ColorVectorSpace", "Colors", "FreeType", "GeometryBasics"]
-git-tree-sha1 = "b5c7fe9cea653443736d264b85466bad8c574f4a"
+git-tree-sha1 = "77e2b094e61d939f9626181ab23d0b76e78f9fd3"
 uuid = "663a7486-cb36-511b-a19d-713bb74d65c9"
-version = "0.9.9"
+version = "0.10.5"
 
 [[deps.FriBidi_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -375,11 +432,16 @@ git-tree-sha1 = "1ed150b39aebcc805c26b93a8d0122c940f64ce2"
 uuid = "559328eb-81f9-559d-9380-de523a88c83c"
 version = "1.0.14+0"
 
+[[deps.GeoFormatTypes]]
+git-tree-sha1 = "59107c179a586f0fe667024c5eb7033e81333271"
+uuid = "68eda718-8dee-11e9-39e7-89f7f65f511f"
+version = "0.4.2"
+
 [[deps.GeoInterface]]
-deps = ["Extents"]
-git-tree-sha1 = "801aef8228f7f04972e596b09d4dba481807c913"
+deps = ["Extents", "GeoFormatTypes"]
+git-tree-sha1 = "826b4fd69438d9ce4d2b19de6bc2f970f45f0f88"
 uuid = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
-version = "1.3.4"
+version = "1.3.8"
 
 [[deps.GeometryBasics]]
 deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
@@ -393,23 +455,17 @@ git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
 uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
 version = "0.21.0+0"
 
-[[deps.Ghostscript_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "43ba3d3c82c18d88471cfd2924931658838c9d8f"
-uuid = "61579ee1-b43e-5ca0-a5da-69d92c66a64b"
-version = "9.55.0+4"
+[[deps.Giflib_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "0224cce99284d997f6880a42ef715a37c99338d1"
+uuid = "59f7168a-df46-5410-90c8-f2779963d0ec"
+version = "5.2.2+0"
 
 [[deps.Glib_jll]]
 deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"]
-git-tree-sha1 = "7c82e6a6cd34e9d935e9aa4051b66c6ff3af59ba"
+git-tree-sha1 = "674ff0db93fffcd11a3573986e550d66cd4fd71f"
 uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
-version = "2.80.2+0"
-
-[[deps.Graphics]]
-deps = ["Colors", "LinearAlgebra", "NaNMath"]
-git-tree-sha1 = "d61890399bc535850c4bf08e4e0d3a7ad0f21cbd"
-uuid = "a2bd30eb-e257-5431-a919-1863eab51364"
-version = "1.1.2"
+version = "2.80.5+0"
 
 [[deps.Graphite2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
@@ -419,9 +475,9 @@ version = "1.3.14+0"
 
 [[deps.GridLayoutBase]]
 deps = ["GeometryBasics", "InteractiveUtils", "Observables"]
-git-tree-sha1 = "f57a64794b336d4990d90f80b147474b869b1bc4"
+git-tree-sha1 = "fc713f007cff99ff9e50accba6373624ddd33588"
 uuid = "3955a311-db13-416c-9275-1d80ed98e5e9"
-version = "0.9.2"
+version = "0.11.0"
 
 [[deps.Grisu]]
 git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
@@ -447,76 +503,64 @@ uuid = "0234f1f7-429e-5d53-9886-15a909be8d59"
 version = "1.14.2+1"
 
 [[deps.HTTP]]
-deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
-git-tree-sha1 = "0fa77022fe4b511826b39c894c90daf5fce3334a"
+deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
+git-tree-sha1 = "1336e07ba2eb75614c99496501a8f4b233e9fafe"
 uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
-version = "0.9.17"
+version = "1.10.10"
 
 [[deps.HarfBuzz_jll]]
-deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"]
-git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3"
+deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"]
+git-tree-sha1 = "401e4f3f30f43af2c8478fc008da50096ea5240f"
 uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
-version = "2.8.1+1"
+version = "8.3.1+0"
 
 [[deps.Hwloc_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "ca0f6bf568b4bfc807e7537f081c81e35ceca114"
+git-tree-sha1 = "50aedf345a709ab75872f80a2779568dc0bb461b"
 uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8"
-version = "2.10.0+0"
+version = "2.11.2+1"
 
 [[deps.HypergeometricFunctions]]
-deps = ["DualNumbers", "LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
-git-tree-sha1 = "f218fe3736ddf977e0e772bc9a586b2383da2685"
+deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
+git-tree-sha1 = "b1c2585431c382e3fe5805874bda6aea90a95de9"
 uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
-version = "0.3.23"
+version = "0.3.25"
 
 [[deps.Hyperscript]]
 deps = ["Test"]
-git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
+git-tree-sha1 = "179267cfa5e712760cd43dcae385d7ea90cc25a4"
 uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
-version = "0.0.4"
+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"]
-git-tree-sha1 = "b51bb8cae22c66d0f6357e3bcb6363145ef20835"
+git-tree-sha1 = "eb49b82c172811fd2c86759fa0553a2221feb909"
 uuid = "c817782e-172a-44cc-b673-b171935fbb9e"
-version = "0.1.5"
+version = "0.1.7"
 
 [[deps.ImageCore]]
-deps = ["AbstractFFTs", "ColorVectorSpace", "Colors", "FixedPointNumbers", "Graphics", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "Reexport"]
-git-tree-sha1 = "acf614720ef026d38400b3817614c45882d75500"
+deps = ["ColorVectorSpace", "Colors", "FixedPointNumbers", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "PrecompileTools", "Reexport"]
+git-tree-sha1 = "8c193230235bbcee22c8066b0374f63b5683c2d3"
 uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
-version = "0.9.4"
+version = "0.10.5"
 
 [[deps.ImageIO]]
-deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs"]
-git-tree-sha1 = "437abb322a41d527c197fa800455f79d414f0a3c"
+deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs", "WebP"]
+git-tree-sha1 = "696144904b76e1ca433b886b4e7edd067d76cbf7"
 uuid = "82e4d734-157c-48bb-816b-45c225c6df19"
-version = "0.6.8"
-
-[[deps.ImageMagick]]
-deps = ["FileIO", "ImageCore", "ImageMagick_jll", "InteractiveUtils", "Libdl", "Pkg", "Random"]
-git-tree-sha1 = "5bc1cb62e0c5f1005868358db0692c994c3a13c6"
-uuid = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
-version = "1.2.1"
-
-[[deps.ImageMagick_jll]]
-deps = ["Artifacts", "Ghostscript_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Zlib_jll", "libpng_jll"]
-git-tree-sha1 = "d65554bad8b16d9562050c67e7223abf91eaba2f"
-uuid = "c73af94c-d91f-53ed-93a7-00f77d67a9d7"
-version = "6.9.13+0"
+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"]
@@ -530,20 +574,15 @@ uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959"
 version = "1.0.0"
 
 [[deps.Inflate]]
-git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381"
+git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d"
 uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
-version = "0.1.4"
-
-[[deps.IniFile]]
-git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625"
-uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
-version = "0.5.1"
+version = "0.1.5"
 
 [[deps.IntelOpenMP_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "be50fe8df3acbffa0274a744f1a99d29c45a57f4"
+deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"]
+git-tree-sha1 = "10bd689145d2c3b2a9844005d01087cc1194e79e"
 uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
-version = "2024.1.0+0"
+version = "2024.2.1+0"
 
 [[deps.InteractiveUtils]]
 deps = ["Markdown"]
@@ -554,27 +593,49 @@ deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArr
 git-tree-sha1 = "88a101217d7cb38a7b481ccd50d21876e1d1b0e0"
 uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
 version = "0.15.1"
+weakdeps = ["Unitful"]
 
     [deps.Interpolations.extensions]
     InterpolationsUnitfulExt = "Unitful"
 
-    [deps.Interpolations.weakdeps]
-    Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
+[[deps.IntervalArithmetic]]
+deps = ["CRlibm_jll", "LinearAlgebra", "MacroTools", "RoundingEmulator"]
+git-tree-sha1 = "24c095b1ec7ee58b936985d31d5df92f9b9cfebb"
+uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
+version = "0.22.19"
+
+    [deps.IntervalArithmetic.extensions]
+    IntervalArithmeticDiffRulesExt = "DiffRules"
+    IntervalArithmeticForwardDiffExt = "ForwardDiff"
+    IntervalArithmeticIntervalSetsExt = "IntervalSets"
+    IntervalArithmeticRecipesBaseExt = "RecipesBase"
+
+    [deps.IntervalArithmetic.weakdeps]
+    DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b"
+    ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
+    IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
+    RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
 
 [[deps.IntervalSets]]
 git-tree-sha1 = "dba9ddf07f77f60450fe5d2e2beb9854d9a49bd0"
 uuid = "8197267c-284f-5f27-9208-e0e47529a953"
 version = "0.7.10"
+weakdeps = ["Random", "RecipesBase", "Statistics"]
 
     [deps.IntervalSets.extensions]
     IntervalSetsRandomExt = "Random"
     IntervalSetsRecipesBaseExt = "RecipesBase"
     IntervalSetsStatisticsExt = "Statistics"
 
-    [deps.IntervalSets.weakdeps]
-    Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
-    RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
-    Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+[[deps.InverseFunctions]]
+git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb"
+uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
+version = "0.1.17"
+weakdeps = ["Dates", "Test"]
+
+    [deps.InverseFunctions.extensions]
+    InverseFunctionsDatesExt = "Dates"
+    InverseFunctionsTestExt = "Test"
 
 [[deps.IrrationalConstants]]
 git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2"
@@ -599,9 +660,9 @@ version = "1.0.0"
 
 [[deps.JLLWrappers]]
 deps = ["Artifacts", "Preferences"]
-git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca"
+git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b"
 uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
-version = "1.5.0"
+version = "1.6.1"
 
 [[deps.JSON]]
 deps = ["Dates", "Mmap", "Parsers", "Unicode"]
@@ -611,9 +672,9 @@ version = "0.21.4"
 
 [[deps.JSON3]]
 deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"]
-git-tree-sha1 = "eb3edce0ed4fa32f75a0a11217433c31d56bd48b"
+git-tree-sha1 = "1d322381ef7b087548321d3f878cb4c9bd8f8f9b"
 uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
-version = "1.14.0"
+version = "1.14.1"
 
     [deps.JSON3.extensions]
     JSON3ArrowExt = ["ArrowTypes"]
@@ -621,12 +682,6 @@ version = "1.14.0"
     [deps.JSON3.weakdeps]
     ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd"
 
-[[deps.JSServe]]
-deps = ["Base64", "CodecZlib", "Colors", "HTTP", "Hyperscript", "JSON3", "LinearAlgebra", "Markdown", "MsgPack", "Observables", "RelocatableFolders", "SHA", "Sockets", "Tables", "Test", "UUIDs", "WebSockets", "WidgetsBase"]
-git-tree-sha1 = "4cd7c5f723cad3cbbdfb295215e45b15b6924a19"
-uuid = "824d6782-a2ef-11e9-3a09-e5662e0c26f9"
-version = "1.2.9"
-
 [[deps.JpegTurbo]]
 deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"]
 git-tree-sha1 = "fa6d0bcff8583bac20f1ffa708c3913ca605c611"
@@ -635,9 +690,9 @@ version = "0.1.5"
 
 [[deps.JpegTurbo_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "c84a835e1a09b289ffcd2271bf2a337bbdda6637"
+git-tree-sha1 = "25ee0be4d43d0269027024d75a24c24d6c6e590c"
 uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
-version = "3.0.3+0"
+version = "3.0.4+0"
 
 [[deps.KernelDensity]]
 deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"]
@@ -652,27 +707,27 @@ uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
 version = "3.100.2+0"
 
 [[deps.LERC_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "36bdbc52f13a7d1dcb0f3cd694e01677a515655b"
 uuid = "88015f11-f218-50d7-93a8-a6af411a945d"
-version = "3.0.0+1"
+version = "4.0.0+0"
 
 [[deps.LLVMOpenMP_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713"
+git-tree-sha1 = "78211fb6cbc872f77cad3fc0b6cf647d923f4929"
 uuid = "1d63c593-3942-5779-bab2-d838dc0a180e"
-version = "15.0.7+0"
+version = "18.1.7+0"
 
 [[deps.LZO_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "70c5da094887fd2cae843b8db33920bac4b6f07d"
+git-tree-sha1 = "854a9c268c43b77b0a27f22d7fab8d33cdb3a731"
 uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
-version = "2.10.2+0"
+version = "2.10.2+1"
 
 [[deps.LaTeXStrings]]
-git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec"
+git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
 uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
-version = "1.3.1"
+version = "1.4.0"
 
 [[deps.LazyArtifacts]]
 deps = ["Artifacts", "Pkg"]
@@ -718,21 +773,27 @@ version = "3.2.2+1"
 
 [[deps.Libgcrypt_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"]
-git-tree-sha1 = "9fd170c4bbfd8b935fdc5f8b7aa33532c991a673"
+git-tree-sha1 = "8be878062e0ffa2c3f67bb58a595375eda5de80b"
 uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
-version = "1.8.11+0"
+version = "1.11.0+0"
+
+[[deps.Libglvnd_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
+git-tree-sha1 = "6f73d1dd803986947b2c750138528a999a6c7733"
+uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
+version = "1.6.0+0"
 
 [[deps.Libgpg_error_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "fbb1f2bef882392312feb1ede3615ddc1e9b99ed"
+git-tree-sha1 = "c6ce1e19f3aec9b59186bdf06cdf3c4fc5f5f3e6"
 uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
-version = "1.49.0+0"
+version = "1.50.0+0"
 
 [[deps.Libiconv_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175"
+git-tree-sha1 = "61dfdba58e585066d8bce214c5a51eaa0539f269"
 uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
-version = "1.17.0+0"
+version = "1.17.0+1"
 
 [[deps.Libmount_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -742,9 +803,9 @@ version = "2.40.1+0"
 
 [[deps.Libtiff_jll]]
 deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"]
-git-tree-sha1 = "6355fb9a4d22d867318db186fd09b09b35bd2ed7"
+git-tree-sha1 = "b404131d06f7886402758c9ce2214b636eb4d54a"
 uuid = "89763e89-9b03-5906-acba-b20f662cd828"
-version = "4.6.0+0"
+version = "4.7.0+0"
 
 [[deps.Libuuid_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -756,17 +817,11 @@ version = "2.40.1+0"
 deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
 uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 
-[[deps.LittleCMS_jll]]
-deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll"]
-git-tree-sha1 = "fa7fd067dca76cadd880f1ca937b4f387975a9f5"
-uuid = "d3a379c0-f9a3-5b72-a4c0-6bf4d2e8af0f"
-version = "2.16.0+0"
-
 [[deps.LogExpFunctions]]
 deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
-git-tree-sha1 = "18144f3e9cbe9b15b070288eef858f71b291ce37"
+git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea"
 uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
-version = "0.3.27"
+version = "0.3.28"
 
     [deps.LogExpFunctions.extensions]
     LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
@@ -781,6 +836,12 @@ version = "0.3.27"
 [[deps.Logging]]
 uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
 
+[[deps.LoggingExtras]]
+deps = ["Dates", "Logging"]
+git-tree-sha1 = "f02b56007b064fbfddb4c9cd60161b6dd0f40df3"
+uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36"
+version = "1.1.0"
+
 [[deps.MAT]]
 deps = ["BufferedStreams", "CodecZlib", "HDF5", "SparseArrays"]
 git-tree-sha1 = "1d2dd9b186742b0f317f2530ddcbf00eebb18e96"
@@ -789,15 +850,15 @@ version = "0.10.7"
 
 [[deps.MKL_jll]]
 deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"]
-git-tree-sha1 = "80b2833b56d466b3858d565adcd16a4a05f2089b"
+git-tree-sha1 = "f046ccd0c6db2832a9f639e2c669c6fe867e5f4f"
 uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
-version = "2024.1.0+0"
+version = "2024.2.0+0"
 
 [[deps.MPICH_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
-git-tree-sha1 = "4099bb6809ac109bfc17d521dad33763bcf026b7"
+git-tree-sha1 = "7715e65c47ba3941c502bffb7f266a41a7f54423"
 uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4"
-version = "4.2.1+1"
+version = "4.2.3+0"
 
 [[deps.MPIPreferences]]
 deps = ["Libdl", "Preferences"]
@@ -807,9 +868,9 @@ version = "0.1.11"
 
 [[deps.MPItrampoline_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
-git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0"
+git-tree-sha1 = "70e830dab5d0775183c99fc75e4c24c614ed7142"
 uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748"
-version = "5.4.0+0"
+version = "5.5.1+0"
 
 [[deps.MacroTools]]
 deps = ["Markdown", "Random"]
@@ -818,17 +879,16 @@ uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
 version = "0.5.13"
 
 [[deps.Makie]]
-deps = ["Animations", "Base64", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Distributions", "DocStringExtensions", "FFMPEG", "FileIO", "FixedPointNumbers", "Formatting", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageIO", "IntervalSets", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MakieCore", "Markdown", "Match", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "Printf", "Random", "RelocatableFolders", "Serialization", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "UnicodeFun"]
-git-tree-sha1 = "b0323393a7190c9bf5b03af442fc115756df8e59"
-pinned = true
+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"
 uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
-version = "0.17.13"
+version = "0.21.16"
 
 [[deps.MakieCore]]
-deps = ["Observables"]
-git-tree-sha1 = "fbf705d2bdea8fc93f1ae8ca2965d8e03d4ca98c"
+deps = ["ColorTypes", "GeometryBasics", "IntervalSets", "Observables"]
+git-tree-sha1 = "ae4dbe0fcf1594ed98594e5f4ee685295a2a6f74"
 uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b"
-version = "0.4.0"
+version = "0.8.10"
 
 [[deps.MappedArrays]]
 git-tree-sha1 = "2dab0221fe2b0f2cb6754eaa743cc266339f527e"
@@ -839,16 +899,11 @@ version = "0.4.2"
 deps = ["Base64"]
 uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
 
-[[deps.Match]]
-git-tree-sha1 = "1d9bc5c1a6e7ee24effb93f175c9342f9154d97f"
-uuid = "7eb4fadd-790c-5f42-8a69-bfa0b872bfbf"
-version = "1.2.0"
-
 [[deps.MathTeXEngine]]
-deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "Test"]
-git-tree-sha1 = "114ef48a73aea632b8aebcb84f796afcc510ac7c"
+deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"]
+git-tree-sha1 = "f45c8916e8385976e1ccd055c9874560c257ab13"
 uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
-version = "0.4.3"
+version = "0.6.2"
 
 [[deps.MbedTLS]]
 deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"]
@@ -898,17 +953,11 @@ git-tree-sha1 = "f5db02ae992c260e4826fe78c942954b48e1d9c2"
 uuid = "99f44e22-a591-53d1-9472-aa23ef4bd671"
 version = "1.2.1"
 
-[[deps.NaNMath]]
-deps = ["OpenLibm_jll"]
-git-tree-sha1 = "0877504529a3e5c3343c6f8b4c0381e57e4387e4"
-uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
-version = "1.0.2"
-
 [[deps.NearestNeighbors]]
 deps = ["Distances", "StaticArrays"]
-git-tree-sha1 = "ded64ff6d4fdd1cb68dfcbb818c69e144a5b2e4c"
+git-tree-sha1 = "8a3271d8309285f4db73b4f662b1b290c715e85e"
 uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
-version = "0.4.16"
+version = "0.4.21"
 
 [[deps.Netpbm]]
 deps = ["FileIO", "ImageCore", "ImageMetadata"]
@@ -920,6 +969,14 @@ version = "1.1.1"
 uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
 version = "1.2.0"
 
+[[deps.NyxWidgets]]
+deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
+git-tree-sha1 = "936f80aa61413c47da00f96abbc0186078698bca"
+repo-rev = "main"
+repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
+uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
+version = "0.1.1"
+
 [[deps.Observables]]
 git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
 uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
@@ -929,14 +986,14 @@ version = "0.5.5"
 deps = ["Observables"]
 git-tree-sha1 = "d54b4c26b41238806c172f85e1913736c2583b0a"
 repo-rev = "main"
-repo-url = "https://gitlab.com/dbc-nyx/observationpolicies.jl"
+repo-url = "https://gitlab.com/dbc-nyx/ObservationPolicies.jl"
 uuid = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
 version = "0.2.4"
 
 [[deps.OffsetArrays]]
-git-tree-sha1 = "e64b4f5ea6b7389f6f046d13d4896a8f9c1ba71e"
+git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e"
 uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
-version = "1.14.0"
+version = "1.14.1"
 weakdeps = ["Adapt"]
 
     [deps.OffsetArrays.extensions]
@@ -955,9 +1012,9 @@ version = "0.3.23+4"
 
 [[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"]
@@ -965,12 +1022,6 @@ git-tree-sha1 = "8292dd5c8a38257111ada2174000a33745b06d4e"
 uuid = "18a262bb-aa17-5467-a713-aee519bc75cb"
 version = "3.2.4+0"
 
-[[deps.OpenJpeg_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "libpng_jll"]
-git-tree-sha1 = "f4cb457ffac5f5cf695699f82c537073958a6a6c"
-uuid = "643b3616-a352-519d-856d-80112ee9badc"
-version = "2.5.2+0"
-
 [[deps.OpenLibm_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
@@ -978,15 +1029,21 @@ version = "0.8.1+2"
 
 [[deps.OpenMPI_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML", "Zlib_jll"]
-git-tree-sha1 = "a9de2f1fc98b92f8856c640bf4aec1ac9b2a0d86"
+git-tree-sha1 = "bfce6d523861a6c562721b262c0d1aaeead2647f"
 uuid = "fe0851c0-eecd-5654-98d4-656369965a5c"
-version = "5.0.3+0"
+version = "5.0.5+0"
+
+[[deps.OpenSSL]]
+deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
+git-tree-sha1 = "38cb508d080d21dc1128f7fb04f20387ed4c0af4"
+uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
+version = "1.4.3"
 
 [[deps.OpenSSL_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "3da7367955dcc5c54c1ba4d402ccdc09a1a3e046"
+git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10"
 uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
-version = "3.0.13+1"
+version = "3.0.15+1"
 
 [[deps.OpenSpecFun_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
@@ -995,10 +1052,10 @@ uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
 version = "0.5.5+0"
 
 [[deps.Opus_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "6703a85cb3781bd5909d48730a67205f3f31a575"
 uuid = "91d4177d-7536-5919-b921-800302f37372"
-version = "1.3.2+0"
+version = "1.3.3+0"
 
 [[deps.OrderedCollections]]
 git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5"
@@ -1024,9 +1081,9 @@ version = "0.4.3"
 
 [[deps.Packing]]
 deps = ["GeometryBasics"]
-git-tree-sha1 = "1155f6f937fa2b94104162f01fa400e192e4272f"
+git-tree-sha1 = "ec3edfe723df33528e085e632414499f26650501"
 uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566"
-version = "0.4.2"
+version = "0.5.0"
 
 [[deps.PaddedViews]]
 deps = ["OffsetArrays"]
@@ -1066,10 +1123,10 @@ uuid = "c2615984-ef14-4d40-b148-916c85b43307"
 version = "0.16.0"
 
 [[deps.PlotUtils]]
-deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "Statistics"]
-git-tree-sha1 = "7b1a9df27f072ac4c9c7cbe5efb198489258d1f5"
+deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "StableRNGs", "Statistics"]
+git-tree-sha1 = "3ca9a356cd2e113c420f2c13bea19f8d3fb1cb18"
 uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
-version = "1.4.1"
+version = "1.4.3"
 
 [[deps.PolygonOps]]
 git-tree-sha1 = "77b3d3605fc1cd0b42d95eba87dfcd2bf67d5ff6"
@@ -1094,26 +1151,32 @@ uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
 
 [[deps.ProgressMeter]]
 deps = ["Distributed", "Printf"]
-git-tree-sha1 = "763a8ceb07833dd51bb9e3bbca372de32c0605ad"
+git-tree-sha1 = "8f6bc219586aef8baf0ff9a5fe16ee9c70cb65e4"
 uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
-version = "1.10.0"
+version = "1.10.2"
 
 [[deps.PtrArrays]]
-git-tree-sha1 = "f011fbb92c4d401059b2212c05c0601b70f8b759"
+git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f"
 uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
-version = "1.2.0"
+version = "1.2.1"
 
 [[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 = "9b23c31e76e333e6fb4c1595ae6afa74966a729e"
+git-tree-sha1 = "cda3b045cf9ef07a08ad46731f5a3165e56cf3da"
 uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
-version = "2.9.4"
+version = "2.11.1"
+
+    [deps.QuadGK.extensions]
+    QuadGKEnzymeExt = "Enzyme"
+
+    [deps.QuadGK.weakdeps]
+    Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
 
 [[deps.Quaternions]]
 deps = ["LinearAlgebra", "Random", "RealDot"]
@@ -1150,6 +1213,12 @@ git-tree-sha1 = "9f0a1b71baaf7650f4fa8a1d168c7fb6ee41f0c9"
 uuid = "c1ae055f-0cd5-4b69-90a6-9a35b1a98df9"
 version = "0.1.0"
 
+[[deps.RecipesBase]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff"
+uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+version = "1.3.4"
+
 [[deps.Reexport]]
 git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
 uuid = "189a3867-3050-52da-a836-e630ba90ab69"
@@ -1157,9 +1226,9 @@ version = "1.2.2"
 
 [[deps.RelocatableFolders]]
 deps = ["SHA", "Scratch"]
-git-tree-sha1 = "307761d71804208c0c62abdbd0ea6822aa5bbefd"
+git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864"
 uuid = "05181044-ff0b-4ac5-8273-598c1e38db00"
-version = "0.2.0"
+version = "1.0.1"
 
 [[deps.Requires]]
 deps = ["UUIDs"]
@@ -1169,27 +1238,30 @@ version = "1.3.0"
 
 [[deps.Rmath]]
 deps = ["Random", "Rmath_jll"]
-git-tree-sha1 = "f65dcb5fa46aee0cf9ed6274ccbd597adc49aa7b"
+git-tree-sha1 = "852bd0f55565a9e973fcfee83a84413270224dc4"
 uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
-version = "0.7.1"
+version = "0.8.0"
 
 [[deps.Rmath_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "d483cd324ce5cf5d61b77930f0bbd6cb61927d21"
+git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8"
 uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
-version = "0.4.2+0"
+version = "0.5.1+0"
 
 [[deps.Rotations]]
 deps = ["LinearAlgebra", "Quaternions", "Random", "StaticArrays"]
 git-tree-sha1 = "5680a9276685d392c87407df00d57c9924d9f11e"
 uuid = "6038ab10-8711-5258-84ad-4b1120ba62dc"
 version = "1.7.1"
+weakdeps = ["RecipesBase"]
 
     [deps.Rotations.extensions]
     RotationsRecipesBaseExt = "RecipesBase"
 
-    [deps.Rotations.weakdeps]
-    RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+[[deps.RoundingEmulator]]
+git-tree-sha1 = "40b9edad2e5287e05bd413a38f61a8ff55b9557b"
+uuid = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705"
+version = "0.2.1"
 
 [[deps.SHA]]
 uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
@@ -1197,9 +1269,9 @@ version = "0.7.0"
 
 [[deps.SIMD]]
 deps = ["PrecompileTools"]
-git-tree-sha1 = "2803cab51702db743f3fda07dd1745aadfbf43bd"
+git-tree-sha1 = "52af86e35dd1b177d051b12681e1c581f53c281b"
 uuid = "fdea26ae-647d-5447-a871-4b548cad5224"
-version = "3.5.0"
+version = "3.7.0"
 
 [[deps.Scratch]]
 deps = ["Dates"]
@@ -1212,9 +1284,9 @@ uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
 
 [[deps.ShaderAbstractions]]
 deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays", "StructArrays", "Tables"]
-git-tree-sha1 = "6b5bba824b515ec026064d1e7f5d61432e954b71"
+git-tree-sha1 = "79123bc60c5507f035e6d1d9e563bb2971954ec8"
 uuid = "65257c39-d410-5151-9873-9b3e5be5013e"
-version = "0.2.9"
+version = "0.4.1"
 
 [[deps.SharedArrays]]
 deps = ["Distributed", "Mmap", "Random", "Serialization"]
@@ -1232,6 +1304,11 @@ git-tree-sha1 = "d263a08ec505853a5ff1c1ebde2070419e3f28e9"
 uuid = "73760f76-fbc4-59ce-8f25-708e95d2df96"
 version = "0.4.0"
 
+[[deps.SimpleBufferStream]]
+git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1"
+uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7"
+version = "1.2.0"
+
 [[deps.SimpleTraits]]
 deps = ["InteractiveUtils", "MacroTools"]
 git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
@@ -1268,6 +1345,12 @@ weakdeps = ["ChainRulesCore"]
     [deps.SpecialFunctions.extensions]
     SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
 
+[[deps.StableRNGs]]
+deps = ["Random"]
+git-tree-sha1 = "83e6cce8324d49dfaf9ef059227f91ed4441a8e5"
+uuid = "860ef19b-820b-49d6-a774-d7a799459cd3"
+version = "1.0.2"
+
 [[deps.StackViews]]
 deps = ["OffsetArrays"]
 git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c"
@@ -1276,9 +1359,9 @@ version = "0.1.1"
 
 [[deps.StaticArrays]]
 deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"]
-git-tree-sha1 = "9ae599cd7529cfce7fea36cf00a62cfc56f0f37c"
+git-tree-sha1 = "777657803913ffc7e8cc20f0fd04b634f871af8f"
 uuid = "90137ffa-7385-5640-81b9-e52037218182"
-version = "1.9.4"
+version = "1.9.8"
 weakdeps = ["ChainRulesCore", "Statistics"]
 
     [deps.StaticArrays.extensions]
@@ -1286,9 +1369,9 @@ weakdeps = ["ChainRulesCore", "Statistics"]
     StaticArraysStatisticsExt = "Statistics"
 
 [[deps.StaticArraysCore]]
-git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d"
+git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682"
 uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
-version = "1.4.2"
+version = "1.4.3"
 
 [[deps.Statistics]]
 deps = ["LinearAlgebra", "SparseArrays"]
@@ -1303,24 +1386,21 @@ version = "1.7.0"
 
 [[deps.StatsBase]]
 deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
-git-tree-sha1 = "d1bf48bfcc554a3761a133fe3a9bb01488e06916"
+git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21"
 uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
-version = "0.33.21"
+version = "0.34.3"
 
 [[deps.StatsFuns]]
 deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
-git-tree-sha1 = "cef0472124fab0695b58ca35a77c6fb942fdab8a"
+git-tree-sha1 = "b423576adc27097764a90e163157bcfc9acf0f46"
 uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
-version = "1.3.1"
+version = "1.3.2"
+weakdeps = ["ChainRulesCore", "InverseFunctions"]
 
     [deps.StatsFuns.extensions]
     StatsFunsChainRulesCoreExt = "ChainRulesCore"
     StatsFunsInverseFunctionsExt = "InverseFunctions"
 
-    [deps.StatsFuns.weakdeps]
-    ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
-    InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
-
 [[deps.StructArrays]]
 deps = ["ConstructionBase", "DataAPI", "Tables"]
 git-tree-sha1 = "f4dc295e983502292c4c3f951dbb4e985e35b3be"
@@ -1341,9 +1421,9 @@ version = "0.6.18"
 
 [[deps.StructTypes]]
 deps = ["Dates", "UUIDs"]
-git-tree-sha1 = "ca4bccb03acf9faaf4137a9abc1881ed1841aa70"
+git-tree-sha1 = "159331b30e94d7b11379037feeb9b690950cace8"
 uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
-version = "1.10.0"
+version = "1.11.0"
 
 [[deps.SuiteSparse]]
 deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
@@ -1366,10 +1446,10 @@ uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
 version = "1.0.1"
 
 [[deps.Tables]]
-deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits"]
-git-tree-sha1 = "cb76cf677714c095e535e3501ac7954732aeea2d"
+deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
+git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297"
 uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
-version = "1.11.1"
+version = "1.12.0"
 
 [[deps.Tar]]
 deps = ["ArgTools", "SHA"]
@@ -1386,25 +1466,30 @@ version = "0.1.1"
 deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
 uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
 
+[[deps.ThreadPools]]
+deps = ["Printf", "RecipesBase", "Statistics"]
+git-tree-sha1 = "50cb5f85d5646bc1422aa0238aa5bfca99ca9ae7"
+uuid = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431"
+version = "2.1.1"
+
 [[deps.TidyObservables]]
 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 = "bc7fd5c91041f44636b2c134041f7e5263ce58ae"
+git-tree-sha1 = "0248b1b2210285652fbc67fd6ced9bf0394bcfec"
 uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
-version = "0.10.0"
+version = "0.11.1"
 
 [[deps.TranscodingStreams]]
-deps = ["Random", "Test"]
-git-tree-sha1 = "9a6ae7ed916312b41236fcef7e0af564ef934769"
+git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742"
 uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
-version = "0.9.13"
+version = "0.11.3"
 
 [[deps.TransformsBase]]
 deps = ["AbstractTrees"]
@@ -1412,6 +1497,11 @@ git-tree-sha1 = "2412fb54902b0063c69c2bcfbec6b571120cc856"
 uuid = "28dd2a49-a57a-4bfb-84ca-1a49db9b96b8"
 version = "0.1.2"
 
+[[deps.TriplotBase]]
+git-tree-sha1 = "4d4ed7f294cda19382ff7de4c137d24d16adc89b"
+uuid = "981d1d27-644d-49a2-9326-4793e63143c3"
+version = "0.1.0"
+
 [[deps.URIs]]
 git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
 uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
@@ -1430,17 +1520,28 @@ git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf"
 uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1"
 version = "0.4.1"
 
+[[deps.Unitful]]
+deps = ["Dates", "LinearAlgebra", "Random"]
+git-tree-sha1 = "d95fe458f26209c66a187b1114df96fd70839efd"
+uuid = "1986cc42-f94f-5a68-af5c-568840ba703d"
+version = "1.21.0"
+weakdeps = ["ConstructionBase", "InverseFunctions"]
+
+    [deps.Unitful.extensions]
+    ConstructionBaseUnitfulExt = "ConstructionBase"
+    InverseFunctionsUnitfulExt = "InverseFunctions"
+
 [[deps.WGLMakie]]
-deps = ["Colors", "FileIO", "FreeTypeAbstraction", "GeometryBasics", "Hyperscript", "ImageMagick", "JSServe", "LinearAlgebra", "Makie", "Observables", "RelocatableFolders", "ShaderAbstractions", "StaticArrays"]
-git-tree-sha1 = "0c7a980515d2072b1bc094668b5ca94671d020de"
+deps = ["Bonito", "Colors", "FileIO", "FreeTypeAbstraction", "GeometryBasics", "Hyperscript", "LinearAlgebra", "Makie", "Observables", "PNGFiles", "PrecompileTools", "RelocatableFolders", "ShaderAbstractions", "StaticArrays"]
+git-tree-sha1 = "8ac9150de978e8215e7c9ab437d6f0a673748999"
 uuid = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"
-version = "0.6.13"
+version = "0.10.16"
 
-[[deps.WebSockets]]
-deps = ["Base64", "Dates", "HTTP", "Logging", "Sockets"]
-git-tree-sha1 = "f91a602e25fe6b89afc93cf02a4ae18ee9384ce3"
-uuid = "104b5d7c-a370-577a-8038-80a2059c5097"
-version = "1.5.9"
+[[deps.WebP]]
+deps = ["CEnum", "ColorTypes", "FileIO", "FixedPointNumbers", "ImageCore", "libwebp_jll"]
+git-tree-sha1 = "aa1ca3c47f119fbdae8770c29820e5e6119b83f2"
+uuid = "e3aaa7dc-3e4b-44e0-be63-ffb868ccd7c1"
+version = "0.1.3"
 
 [[deps.WidgetsBase]]
 deps = ["Observables"]
@@ -1456,21 +1557,21 @@ version = "1.0.0"
 
 [[deps.XML2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"]
-git-tree-sha1 = "52ff2af32e591541550bd753c0da8b9bc92bb9d9"
+git-tree-sha1 = "6a451c6f33a176150f315726eba8b92fbfdb9ae7"
 uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
-version = "2.12.7+0"
+version = "2.13.4+0"
 
 [[deps.XSLT_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"]
-git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a"
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "XML2_jll", "Zlib_jll"]
+git-tree-sha1 = "a54ee957f4c86b526460a720dbc882fa5edcbefc"
 uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
-version = "1.1.34+0"
+version = "1.1.41+0"
 
 [[deps.XZ_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "ac88fb95ae6447c8dda6a5503f3bafd496ae8632"
+git-tree-sha1 = "15e637a697345f6743674f1322beefbc5dcd5cfc"
 uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800"
-version = "5.4.6+0"
+version = "5.6.3+0"
 
 [[deps.Xorg_libX11_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
@@ -1510,9 +1611,9 @@ version = "0.1.1+0"
 
 [[deps.Xorg_libxcb_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
-git-tree-sha1 = "b4bfde5d5b652e22b9c790ad00af08b6d042b97d"
+git-tree-sha1 = "bcd466676fef0878338c61e655629fa7bbc69d8e"
 uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
-version = "1.15.0+0"
+version = "1.17.0+0"
 
 [[deps.Xorg_xtrans_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -1527,9 +1628,9 @@ version = "1.2.13+1"
 
 [[deps.Zstd_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b"
+git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b"
 uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
-version = "1.5.6+0"
+version = "1.5.6+1"
 
 [[deps.isoband_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
@@ -1550,39 +1651,45 @@ uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b"
 version = "3.9.0+0"
 
 [[deps.libass_jll]]
-deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
-git-tree-sha1 = "5982a94fcba20f02f42ace44b9894ee2b140fe47"
+deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "e17c115d55c5fbb7e52ebedb427a0dca79d4484e"
 uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
-version = "0.15.1+0"
+version = "0.15.2+0"
 
 [[deps.libblastrampoline_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
-version = "5.8.0+1"
+version = "5.11.0+0"
 
 [[deps.libfdk_aac_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "daacc84a041563f965be61859a36e17c4e4fcd55"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "8a22cf860a7d27e4f3498a0fe0811a7957badb38"
 uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
-version = "2.0.2+0"
+version = "2.0.3+0"
 
 [[deps.libpng_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
-git-tree-sha1 = "d7015d2e18a5fd9a4f47de711837e980519781a4"
+git-tree-sha1 = "b70c870239dc3d7bc094eb2d6be9b73d27bef280"
 uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
-version = "1.6.43+1"
+version = "1.6.44+0"
 
 [[deps.libsixel_jll]]
 deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "libpng_jll"]
-git-tree-sha1 = "d4f63314c8aa1e48cd22aa0c17ed76cd1ae48c3c"
+git-tree-sha1 = "7dfa0fd9c783d3d0cc43ea1af53d69ba45c447df"
 uuid = "075b6546-f08a-558a-be8f-8157d0f608a5"
-version = "1.10.3+0"
+version = "1.10.3+1"
 
 [[deps.libvorbis_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
-git-tree-sha1 = "b910cb81ef3fe6e78bf6acee440bda86fd6ae00c"
+git-tree-sha1 = "490376214c4721cdaca654041f635213c6165cb3"
 uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
-version = "1.3.7+1"
+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"
+uuid = "c5f90fcd-3b7e-5836-afba-fc50a0988cb2"
+version = "1.4.0+0"
 
 [[deps.nghttp2_jll]]
 deps = ["Artifacts", "Libdl"]
@@ -1601,13 +1708,13 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
 version = "17.4.0+2"
 
 [[deps.x264_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "4fea590b89e6ec504593146bf8b988b2c00922b2"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "35976a1216d6c066ea32cba2150c4fa682b276fc"
 uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
-version = "2021.5.5+0"
+version = "10164.0.0+0"
 
 [[deps.x265_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "ee567a171cce03570d77ad3a43e90218e38937a9"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "dcc541bb19ed5b0ede95581fb2e41ecf179527d2"
 uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
-version = "3.5.0+0"
+version = "3.6.0+0"
diff --git a/Project.toml b/Project.toml
index 115c84b..64da699 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,19 +1,20 @@
 name = "LarvaTagger"
 uuid = "8b3b36f1-dfed-446e-8561-ea19fe966a4d"
 authors = ["François Laurent", "Institut Pasteur"]
-version = "0.18.5"
+version = "0.19.0"
 
 [deps]
+Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
 Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
 Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
 DocOpt = "968ba79b-81e4-546f-ab3a-2eecfa62a9db"
 Format = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
-JSServe = "824d6782-a2ef-11e9-3a09-e5662e0c26f9"
 LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
 Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
 Meshes = "eacbb407-ea5a-433e-ab97-5258b1ca43fa"
 NearestNeighbors = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
+NyxWidgets = "c288fd06-43d3-4b04-8307-797133353e2e"
 Observables = "510215fc-4207-5dde-b226-833fc4488ee2"
 ObservationPolicies = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
 OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
@@ -25,8 +26,11 @@ TidyObservables = "c8131bbd-73a8-4254-a42d-d5d4c5febb31"
 WGLMakie = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"
 
 [compat]
-Makie = "< 0.18.0"
+Bonito = "< 4.0.0"
+NyxWidgets = "0.1.1"
+ObservationPolicies = "0.2.4"
 PlanarLarvae = ">= 0.11.2"
+TidyObservables = "0.1.1"
 julia = "1.6"
 
 [extras]
diff --git a/README.md b/README.md
index 5636442..eaf29e8 100644
--- a/README.md
+++ b/README.md
@@ -143,6 +143,7 @@ mkdir LarvaTagger && cd $_ && \
 julia --project=. -e 'using Pkg; Pkg.add([ \
   PackageSpec(url="https://gitlab.com/dbc-nyx/ObservationPolicies.jl"), \
   PackageSpec(url="https://gitlab.com/dbc-nyx/TidyObservables.jl"), \
+  PackageSpec(url="https://gitlab.com/dbc-nyx/NyxWidgets.jl"), \
   PackageSpec(url="https://gitlab.pasteur.fr/nyx/PlanarLarvae.jl"), \
   PackageSpec(url="https://gitlab.pasteur.fr/nyx/LarvaTagger.jl")])'
 ```
diff --git a/scripts/larvatagger b/scripts/larvatagger
index b46c90c..5a0a65a 100755
--- a/scripts/larvatagger
+++ b/scripts/larvatagger
@@ -52,7 +52,7 @@ import|merge|--version|-V)
 LarvaTagger
 
 Usage:
-  larvatagger open <file-path> [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
+  larvatagger open [<file-path>] [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
   larvatagger import <input-path> [<output-file>] [--id=<id>] [--framerate=<fps>] [--pixelsize=<μm>] [--overrides=<comma-separated-list>] [--default-label=<label>] [--manual-label=<label>] [--decode] [--copy-labels]
   larvatagger train <backend-path> <data-path> <model-instance> [--pretrained-model=<instance>] [--labels=<comma-separated-list>] [--sample-size=<N>] [--balancing-strategy=<strategy>] [--class-weights=<csv>] [--manual-label=<label>] [--layers=<N>] [--iterations=<N>] [--seed=<seed>] [--debug]
   larvatagger train <backend-path> <data-path> <model-instance> --fine-tune=<instance> [--balancing-strategy=<strategy>] [--manual-label=<label>] [--iterations=<N>] [--seed=<seed>] [--debug]
@@ -101,6 +101,7 @@ Commands:
 
   open      Launch the server-based GUI.
 
+    The optional positional argument <file-path> can also be the data root directory.
     Backends defined in LarvaTagger project root directory are automatically found. Other
     backend locations can be specified with the --backends argument.
 
diff --git a/scripts/larvatagger-gui.jl b/scripts/larvatagger-gui.jl
index 1bc01e8..36ac2ab 100755
--- a/scripts/larvatagger-gui.jl
+++ b/scripts/larvatagger-gui.jl
@@ -9,7 +9,9 @@ fi
 FLAGS=
 if [ "$1" = "--sysimage" -o "$1" = "-J" ]; then FLAGS="--sysimage $2 "; shift 2; fi
 if [ "${1:0:2}" = "-J" ]; then FLAGS="$1 "; shift; fi
-if [ -n "$1" -a -f "$1" ]; then FLAGS="$FLAGS -iq "; fi
+HELP=
+for i in "$@"; do if [ "$i" = "-h" -o "$i" = "--help" ]; then HELP=1; break; fi; done
+if [ -z $HELP ]; then FLAGS="$FLAGS -iq "; fi
 if [ -z "$JULIA" ]; then JULIA=julia; fi
     exec $JULIA --project="$PROJECT_DIR" --color=yes --startup-file=no $FLAGS\
     "${BASH_SOURCE[0]}" "$@"
diff --git a/src/LarvaTagger.jl b/src/LarvaTagger.jl
index b3484f9..00d344c 100644
--- a/src/LarvaTagger.jl
+++ b/src/LarvaTagger.jl
@@ -3,13 +3,13 @@ module LarvaTagger
 using PlanarLarvae, PlanarLarvae.Datasets, PlanarLarvae.Formats
 using ObservationPolicies
 using TidyObservables
+using NyxWidgets, NyxWidgets.Players, NyxWidgets.FileBrowsers, NyxWidgets.FilePickers
+import NyxWidgets.Base: lowerdom, dom_id, dom_selector
 
 using Logging
-Logging.disable_logging(Logging.Warn) # prior to loading old libraries
-using JSServe, WGLMakie
-using JSServe: evaljs, onjs
+using Bonito, WGLMakie
+using Bonito: evaljs, onjs
 using Makie
-Logging.disable_logging(Logging.Debug) # restore default
 using Format
 using Colors
 using StaticArrays
diff --git a/src/cli.jl b/src/cli.jl
index c0a25fb..23e8fdb 100644
--- a/src/cli.jl
+++ b/src/cli.jl
@@ -9,7 +9,7 @@ using .Toolkit
 usage = """Larva Tagger.
 
 Usage:
-  larvatagger.jl open <file-path> [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
+  larvatagger.jl open [<file-path>] [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
   larvatagger.jl import <input-path> [<output-file>] [--id=<id>] [--framerate=<fps>] [--pixelsize=<μm>] [--overrides=<comma-separated-list>] [--default-label=<label>] [--manual-label=<label>] [--decode] [--copy-labels]
   larvatagger.jl train <backend-path> <data-path> <model-instance> [--pretrained-model=<instance>] [--labels=<comma-separated-list>] [--sample-size=<N>] [--balancing-strategy=<strategy>] [--class-weights=<csv>] [--manual-label=<label>] [--layers=<N>] [--iterations=<N>] [--seed=<seed>] [--debug]
   larvatagger.jl train <backend-path> <data-path> <model-instance> --fine-tune=<instance> [--balancing-strategy=<strategy>] [--manual-label=<label>] [--iterations=<N>] [--seed=<seed>] [--debug]
@@ -58,6 +58,7 @@ Commands:
 
   open      Launch the server-based GUI.
 
+    The optional positional argument <file-path> can also be the data root directory.
     Backends defined in LarvaTagger project root directory are automatically found. Other
     backend locations can be specified with the --backends argument.
 
diff --git a/src/cli_open.jl b/src/cli_open.jl
index f46a52a..6f10ed6 100644
--- a/src/cli_open.jl
+++ b/src/cli_open.jl
@@ -2,14 +2,14 @@ module GUI
 
 using DocOpt
 using LarvaTagger
-using JSServe: JSServe, Server
+using Bonito: Bonito, Server
 
 export main
 
 usage = """LarvaTagger.jl - launch the server-based GUI.
 
 Usage:
-  larvatagger-gui.jl <file-path> [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
+  larvatagger-gui.jl [<file-path>] [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
   larvatagger-gui.jl -h | --help
 
 Options:
@@ -22,6 +22,7 @@ Options:
   --view-factor=<real>    Scaling factor for the larva views; default is 2 on macOS, 1 elsewhere.
   --manual-label=<label>  Secondary label for manually labelled data [default: edited].
 
+The optional positional argument <file-path> can also be the data root directory.
 Backends defined in LarvaTagger project root directory are automatically found. Other
 backend locations can be specified with the --backends argument.
 """
@@ -47,9 +48,17 @@ function main(args=ARGS; exit_on_error=false)
 
     verbose = !parsed_args["--quiet"]
     infile = parsed_args["<file-path>"]
-    if !isfile(infile)
-        @error "File not found; did you specify a file path?" infile
-        exit()
+    if isempty(infile)
+        infile = nothing
+    elseif !isfile(infile)
+        if isdir(infile)
+            dataroot = infile
+            infile = nothing
+            cd(dataroot)
+        else
+            @error "File not found; did you specify a file path?" infile
+            exit()
+        end
     end
 
     kwargs = Dict{Symbol, Any}()
@@ -75,7 +84,7 @@ function main(args=ARGS; exit_on_error=false)
     port = isnothing(port) ? 9284 : parse(Int, port)
     server = Server(app, "0.0.0.0", port)
     if parsed_args["--browser"]
-        JSServe.openurl("http://127.0.0.1:$(port)")
+        Bonito.openurl("http://127.0.0.1:$(port)")
     end
     if verbose
         @info "The server is ready at http://127.0.0.1:$(port)"
diff --git a/src/controllers.jl b/src/controllers.jl
index bf5906b..0482579 100644
--- a/src/controllers.jl
+++ b/src/controllers.jl
@@ -170,7 +170,7 @@ struct LarvaController
     model::Dict{<:Integer, LarvaModel}
     tag_lut::AbstractObservable{<:TagLUT}
     activelarva::AbstractObservable{<:ActiveLarva}
-    player::AbstractAnimator
+    player::AbstractPlayer
     # could make LarvaController mutable instead...
     boundingbox::Ref{<:NTuple{4, <:AbstractFloat}}
     medianlarvasize::AbstractFloat
@@ -179,7 +179,7 @@ end
 function LarvaController(main::ControllerHub,
         model::Dict{<:Integer, LarvaModel},
         tag_lut::AbstractObservable{<:TagLUT},
-        player::AbstractAnimator)
+        player::AbstractPlayer)
     activelarva = Observable{ActiveLarva}(nothing)
     boundingbox = Ref((0.0, 10.0, 0.0, 10.0))
     mediansize = medianlarvasize(model)
@@ -190,7 +190,7 @@ function LarvaController(main::ControllerHub,
         model::Dict{<:Integer, LarvaModel},
         tag_lut::TagLUT,
         times::Vector{PlanarLarvae.Time})
-    player = playcontroller(times)
+    player = timecontroller(times)
     LarvaController(main, model, Observable(tag_lut), player)
 end
 
@@ -216,7 +216,7 @@ setboundingbox!(c::LarvaController, bb) = (c.boundingbox[] = bb)
 getmedianlarvasize(c) = getmedianlarvasize(gethub(c)[:larva])
 getmedianlarvasize(c::LarvaController) = c.medianlarvasize
 
-getplayer(c::AbstractAnimator) = c
+getplayer(c::AbstractPlayer) = c
 getplayer(c::LarvaController) = c.player
 getplayer(c) = getplayer(gethub(c))
 getplayer(hub::ControllerHub) = haskey(hub, :player) ? hub[:player] : getplayer(hub[:larva])
@@ -307,9 +307,9 @@ end
 setbounds!(view::Axis, lb, ub) = limits!(view, lb[1], ub[1], lb[2], ub[2])
 
 settimebounds!(c, larva) = settimebounds!(getplayer(c), larva)
-settimebounds!(::AbstractAnimator, _) = nothing
+settimebounds!(::AbstractPlayer, _) = nothing
 unsettimebounds!(c) = unsettimebounds!(getplayer(c))
-unsettimebounds!(::AbstractAnimator) = nothing
+unsettimebounds!(::AbstractPlayer) = nothing
 
 function slave(master::Observable, policy::ObservationPolicy=IndependentObservables())
     newobservable(policy, master)
@@ -362,8 +362,6 @@ function slave(master::LarvaController,
                     master.medianlarvasize)
 end
 
-stop!(controller::LarvaController) = stop!(controller.player)
-
 # history
 
 transactions(c) = get!(gethub(c), :transactions, Transaction[])
diff --git a/src/editor.jl b/src/editor.jl
index 61c3388..abf8b40 100644
--- a/src/editor.jl
+++ b/src/editor.jl
@@ -10,13 +10,12 @@ struct EditorView
     twooptiondialog::TwoOptionDialog
 end
 
-function JSServe.jsrender(session::Session, ev::EditorView)
+function Bonito.jsrender(session::Session, ev::EditorView)
     r(session, LarvaTaggerJS)
-    r(session, LarvaTaggerCSS)
     r(session, LoadAwesomeCSS)
-    r(session, JSServe.TailwindCSS)
+    r(session, Bonito.TailwindCSS)
     evaljs(session, js"window.addEventListener('beforeunload', LarvaTagger.confirmDialog)")
-    r(session,
+    dom = r(session,
       DOM.div(ev.larvaviewer,
               cp(ControlPanel(ev.tagfilter; id="tag-panel", title="Tags"),
                  ControlPanel(ev.larvafilter; id="larva-panel", title="Tracks"),
@@ -25,6 +24,8 @@ function JSServe.jsrender(session::Session, ev::EditorView)
               ev.loadanimation,
               ev.twooptiondialog;
               class="flex flex-row"))
+    r(session, LarvaTaggerCSS)
+    return dom
 end
 
 projectdir = dirname(Base.active_project())
@@ -33,16 +34,17 @@ function larvaeditor(path=nothing;
         allow_multiple_tags::Union{Nothing, Bool}=nothing,
         backend_directory::AbstractString=projectdir,
         manualtag::Union{Nothing, String, Symbol}="edited",
+        title="LarvaTagger",
         kwargs...)
 
     # to (re-)load a file, the app is reloaded with the filepath as sole information
     # from previous session
     input = Ref{Union{Nothing, String}}(path)
 
-    App() do session::Session
+    App(title=title) do session::Session
 
         controller = ControllerHub()
-        # used for method dispatch (here `Session` is taken to represent JSServe)
+        # used for method dispatch (here `Session` is taken to represent Bonito)
         controller[:frontend] = Session
 
         tryopenfile(controller, input)
@@ -59,7 +61,7 @@ function larvaeditor(path=nothing;
                             loadanimation(controller),
                             twooptiondialog(controller))
 
-        dom = JSServe.jsrender(session, editor)
+        dom = Bonito.jsrender(session, editor)
 
         turn_load_animation_off(controller)
 
diff --git a/src/files.jl b/src/files.jl
index 273e385..397c781 100644
--- a/src/files.jl
+++ b/src/files.jl
@@ -1,4 +1,5 @@
 
+# deprecated; may be replaced by FileBrowsers.Model.FileBrowser
 struct WorkingDirectory
     controller
     root::String # could be hidden (not stored)
@@ -20,40 +21,40 @@ function workingdir(controller, root::String, path::String=""; secure::Bool=true
         startswith(path, "..") && throw(ArgumentError("path not below root: $path"))
     end
     wd = WorkingDirectory(controller, root, path)
-    on(wd.path, Val(1)) do newpath
-        try
-            if isempty(newpath)
-                newpath = "."
-            elseif isabspath(newpath)
-                fullpath = newpath
-                newpath = relpath(newpath, root)
-            end
-            fullpath = joinpath(root, newpath)
-            if secure
-                fullpath = realpath(fullpath)
-            end
-            if startswith(relpath(fullpath, root), "..")
-                @logmsg SecurityAlert "Path outside of chroot" path=newpath client=identifyclient(controller)
-                throw(Exception)
-            elseif !isdir(fullpath)
-                @debug "Path is not a directory" path=newpath
-                tryopenfile(controller, fullpath; reload=true)
-                throw(Exception)
-            end
-            return Validate(newpath)
-        catch
-            return Invalidate()
-        end
-    end
-    on(wd.path.model) do dir
-        @assert !isempty(dir)
-        content = [entry for entry in readdir(joinpath(root, dir))
-                   if entry[1] != '.']
-        if dir != "."
-            pushfirst!(content, "..")
-        end
-        wd.content[] = content
-    end
+    # on(wd.path, Val(1)) do newpath
+    #     try
+    #         if isempty(newpath)
+    #             newpath = "."
+    #         elseif isabspath(newpath)
+    #             fullpath = newpath
+    #             newpath = relpath(newpath, root)
+    #         end
+    #         fullpath = joinpath(root, newpath)
+    #         if secure
+    #             fullpath = realpath(fullpath)
+    #         end
+    #         if startswith(relpath(fullpath, root), "..")
+    #             @logmsg SecurityAlert "Path outside of chroot" path=newpath client=identifyclient(controller)
+    #             throw(Exception)
+    #         elseif !isdir(fullpath)
+    #             @debug "Path is not a directory" path=newpath
+    #             tryopenfile(controller, fullpath; reload=true)
+    #             throw(Exception)
+    #         end
+    #         return Validate(newpath)
+    #     catch
+    #         return Invalidate()
+    #     end
+    # end
+    # on(wd.path.model) do dir
+    #     @assert !isempty(dir)
+    #     content = [entry for entry in readdir(joinpath(root, dir))
+    #                if entry[1] != '.']
+    #     if dir != "."
+    #         pushfirst!(content, "..")
+    #     end
+    #     wd.content[] = content
+    # end
     return wd
 end
 
@@ -483,3 +484,25 @@ function getoutputfile(controller)
     return outputfile
 end
 
+const valid_inputfile_formats = (".mat", ".spine", ".outline", ".csv", ".label", ".json",
+                                 ".labels")
+
+function saveinputfile(path, content)
+    parts = splitext(path)
+    if length(parts) == 2 && parts[2] in valid_inputfile_formats
+        if ispath(path)
+            if isfile(path)
+                @warn "File already exists" path
+            else
+                @error "File already exists and is not a regular file" path
+                return
+            end
+        end
+        open(path, "w") do f
+            write(f, content)
+        end
+        Taggers.check_permissions(path)
+    else
+        @error "File format not admitted" fileparts=parts
+    end
+end
diff --git a/src/larvatagger.css b/src/larvatagger.css
index 6d9bfd0..0ead180 100644
--- a/src/larvatagger.css
+++ b/src/larvatagger.css
@@ -58,6 +58,11 @@ canvas {
 	background: var(--theme-main-color);
 }
 
+.cp-tab .cp-tab-switch:checked ~ .control-panel .nyx-icon-button div {
+  /* restore default style, otherwise overridden by an above rule */
+  background-color: var(--nyx-icon-fill-color);
+}
+
 div.scrollable {
 	position: relative;
 	margin-right: 0;
@@ -87,16 +92,18 @@ table {
 	width: 30rem;
 }
 
+.nyx-filebrowser {
+  width: unset;
+  box-shadow: unset;
+  padding: unset;
+  margin: unset;
+}
+
 #larva-panel th {
 	padding-left: 1rem;
 	padding-right: 1rem;
 }
 
-#filemenu-panel input:first-child {
-	border-width: 0.0625rem;
-	padding: 0.2rem;
-}
-
 #metadata-panel input {
 	border-width: 0.0625rem;
 	margin: 0.0625rem 0.0625rem 0 0;
@@ -175,12 +182,9 @@ input[type=checkbox] {
 	border-width: 8px;
 }
 
-.player button {
+.nyx-player {
 	margin-left: 0.25rem;
 }
-.player input[type=range]:focus {
-	outline: none;
-}
 
 #tag-selector {
 	width: 100%;
diff --git a/src/larvatagger.js b/src/larvatagger.js
index 51b1478..d65fb7d 100644
--- a/src/larvatagger.js
+++ b/src/larvatagger.js
@@ -1,8 +1,4 @@
 const LarvaTagger = (function () {
-	function toggle(jlstate) {
-		const buttonstate = JSServe.get_observable(jlstate);
-		JSServe.update_obs(jlstate, !buttonstate);
-	}
 
 	function timeSlider(element, input, stepmin, stepmax) {
 		const cooldown = 0.1; // in seconds
@@ -24,21 +20,21 @@ const LarvaTagger = (function () {
 				slavepos = heldpos;
 				heldpos = null;
 				// check bounds
-				mastermax = JSServe.get_observable(stepmax);
+				mastermax = stepmax.value;
 				if (mastermax < slavepos) {
 					slavepos = mastermax;
 					element.value = String(slavepos);
 				} else {
-					mastermin = JSServe.get_observable(stepmin);
+					mastermin = stepmin.value;
 					if (slavepos < mastermin) {
 						slavepos = mastermin;
 						element.value = String(slavepos);
 					}
 				}
 				// update
-				masterpos = JSServe.get_observable(input);
+				masterpos = input.value;
 				if (slavepos != masterpos) {
-					JSServe.update_obs(input, slavepos);
+					input.notify(slavepos);
 				}
 				// start cooldown
 				window.setTimeout(this.timeout, cooldown);
@@ -55,7 +51,7 @@ const LarvaTagger = (function () {
 	function focusOnTimeSlider(view) {
 		if (view === undefined)
 			view = document.getElementById("trackviewer");
-		view.querySelector("input[type=range]").focus()
+		//view.querySelector("input[type=range]").focus()
 	}
 
 	function discardLarvaEdits(checkbox, observer, label) {
@@ -64,7 +60,7 @@ const LarvaTagger = (function () {
 		} else {
 			var answer = confirm(`Discard changes for larva ${label}?`);
 			if (answer) {
-				JSServe.update_obs(observer, false);
+				observer.notify(false);
 			} else {
 				checkbox.checked = true;
 			}
@@ -79,7 +75,7 @@ const LarvaTagger = (function () {
 			const includeallcheckbox = document.getElementById('includeall');
 			includeallcheckbox.checked = false;
 		}
-		JSServe.update_obs(jlstate, jsstate.checked);
+		jlstate.notify(jsstate.checked);
 	}
 
 	function includeAllLarvae(jsstate, jlstate, button) {
@@ -91,7 +87,7 @@ const LarvaTagger = (function () {
 			button = document.getElementById('saveas');
 		}
 		button.disabled = !jsstate.checked;
-		JSServe.update_obs(jlstate, jsstate.checked);
+		jlstate.notify(jsstate.checked);
 	}
 
 	function insertNewTag(table, html) {
@@ -111,22 +107,24 @@ const LarvaTagger = (function () {
 		jlTagSelection = obs;
 	}
 	function toggleTagAtPointer(event) {
-		var that = event.target,
-		    scroll = that.parentElement.scrollTop;
-		event.preventDefault();
-		that.selected = !that.selected;
-		setTimeout(function(){that.parentElement.scrollTop = scroll;}, 0);
-		JSServe.update_obs(jlTagSelection, that.value);
+    if (event !== undefined) {
+      var that = event.target,
+          scroll = that.parentElement.scrollTop;
+      event.preventDefault();
+      that.selected = !that.selected;
+      setTimeout(function(){that.parentElement.scrollTop = scroll;}, 0);
+      jlTagSelection.notify(that.value);
+    }
 		return false;
 	}
 
 	function setOutputFilename(obs) {
-		var defaultfilepath = JSServe.get_observable(obs);
+		var defaultfilepath = obs.value;
 		if (defaultfilepath === null) {
 			defaultfilepath = "{yyyymmdd_HHMMSS}.label";
 		}
 		let filepath = prompt("File path: ", defaultfilepath);
-		JSServe.update_obs(obs, filepath);
+		obs.notify(filepath);
 	}
 
 	function updateSelectOptions(selectElement, selectOptions, jlObserver) {
@@ -139,7 +137,7 @@ const LarvaTagger = (function () {
 			option.text = selectOptions[i];
 			if (jlObserver !== undefined) {
 				option.ondblclick = () => {
-					JSServe.update_obs(jlObserver, selectOptions[i]);
+					jlObserver.notify(selectOptions[i]);
 				};
 			}
 			selectElement.append(option);
@@ -154,7 +152,7 @@ const LarvaTagger = (function () {
 			let files = Array.from(input.files);
 			if (files.length > 0) {
 				console.log(files);
-				JSServe.update_obs(jlObserver, files);
+				jlObserver.notify(files);
 			}
 		};
 		input.click();
@@ -179,7 +177,6 @@ const LarvaTagger = (function () {
 	}
 
 	return {
-		toggle,
 		timeSlider,
 		focusOnTimeSlider,
 		discardLarvaEdits,
diff --git a/src/models.jl b/src/models.jl
index 2c53741..9910a28 100644
--- a/src/models.jl
+++ b/src/models.jl
@@ -310,7 +310,8 @@ end
 
 medianlarvasize(larvae::Dict; n::Int=100) = medianlarvasize(collect(values(larvae)); n=n)
 
-function medianlarvasize(larvae::Vector{LarvaModel}; n::Int=100)
+function medianlarvasize(larvae::Vector{LarvaModel}; n::Int=100)::Float32
+    isempty(larvae) && return 0.0f0
     larvae = Random.shuffle(larvae)
     sizes = Float64[]
     sizehint!(sizes, n)
@@ -324,7 +325,8 @@ function medianlarvasize(larvae::Vector{LarvaModel}; n::Int=100)
     return median(sizes)
 end
 
-function simultaneouslarvae(larvae)
+function simultaneouslarvae(larvae)::Int
+    isempty(larvae) && return 0
     laststep = maximum([larva.alignedsteps[end] for larva in values(larvae)])
     n = 0
     for step in 1:20:laststep
diff --git a/src/players.jl b/src/players.jl
index ff1f5fd..0cf3f3a 100644
--- a/src/players.jl
+++ b/src/players.jl
@@ -1,250 +1,71 @@
 
-abstract type AbstractAnimator end
-
-function stop! end
-function play! end
-function pause! end
-function playpause! end
-function isplaying end
-
-"""
-Plans are to make `Animator` not mutable; don't rely on its mutability.
-"""
-mutable struct Animator <: AbstractAnimator
-    times::Vector{PlanarLarvae.Time}
-    timestep::Observable
-    speed::Observable
-    isplaying::Observable
-end
-
-function playcontroller end
-
-function bind! end
-
-function playercontrols end
-
-## implementation
-
 const O{T} = Union{T, Observable{T}}
+using PlanarLarvae: Time
 asobservable(arg) = Observable(arg)
 asobservable(arg::Observable) = arg
 
-Animator(times::Vector{PlanarLarvae.Time},
-         step::O{Int}=1,
-         speed::O{<:AbstractFloat}=1.0,
-        ) = Animator(times,
-                     asobservable(step),
-                     asobservable(speed),
-                     Observable(false),
-                    )
-
-isplaying(controller::AbstractAnimator) = controller.isplaying[]
-isplaying!(controller::AbstractAnimator, state::Bool) = (controller.isplaying[] = state)
-
-function playnow!(controller::AbstractAnimator)
-    @info "Playing" speed=controller.speed[]
-    ts = controller.times
-    step = controller.timestep[]
-    stop = length(ts)
-    #
-    lag, n = 0, 0
-    clock_t = time()
-    #
-    while step<stop
-        t, t′ = ts[step], ts[step + 1]
-        dt = t′ - t
-        dt′= dt / controller.speed[] - lag
-        0 < dt′ && sleep(dt′)
-        #
-        step′ = ObservationPolicies.input(controller.timestep)[]
-        if isplaying(controller)
-            if step == step′
-                step′ = step + 1
-                @async controller.timestep[] = step′
-            end
-        else
-            break
-        end
-        # adjust lag correction
-        clock_t′ = time()
-        lag′= clock_t′- clock_t - dt
-        lag = (lag * n + lag′) / (n + 1)
-        @debug "Step timing" expected=dt actual=clock_t′-clock_t adjustment=lag
-        #
-        step = step′
-        clock_t = clock_t′
-        n += 1
-    end
-    @info "Pause/stop"
-    pause!(controller)
-end
-
-function bind!(controller::Animator)
-    on(controller.isplaying) do playnow
-        if playnow
-            @async playnow!(controller)
-        end
-    end
-    return controller
-end
-
-function bind!(controller::Animator, timeslider::Makie.Slider)
-    controller.timestep = timeslider.selected_index
-    #bind!(controller)
-end
-
-function bind!(controller::Animator, scene)
-    on(events(scene).window_open) do isopen
-        if !isopen
-            stop!(controller)
-        end
-    end
-    return controller
-end
-
-play!(controller::AbstractAnimator) = (controller.isplaying[] = true)
-pause!(controller::AbstractAnimator) = (controller.isplaying[] = false)
-playpause!(controller::AbstractAnimator) = (controller.isplaying[] = !controller.isplaying[])
-stop!(controller) = pause!(controller)
-
-function playcontroller(times; step::O{Int}=1, speed::O{<:AbstractFloat}=1.0)
-    controller = Animator(times, step, speed)
-    bind!(controller)
-    return controller
-end
-
-gettime(controller::AbstractAnimator, step::Int) = controller.times[step]
-
-struct TimeController <: AbstractAnimator
-    times::Vector{Float64}
-    time::AbstractObservable{Float64}
-    timestep::AbstractObservable{Int64}
+struct TimeController <: AbstractPlayer
+    player::AbstractPlayer
     stepmin::AbstractObservable{Int64}
     stepmax::AbstractObservable{Int64}
     boundreached::AbstractObservable{Bool}
-    playing::AbstractObservable{Bool}
-    speed::AbstractObservable{Float64}
 end
 
-isplaying(controller::TimeController) = controller.playing[]
-play!(controller::TimeController) = (controller.playing[] = true)
-pause!(controller::TimeController) = (controller.playing[] = false)
-playpause!(controller::TimeController) = (controller.playing[] = !controller.playing[])
-
-function stepforward!(controller::TimeController)
-    step = controller.timestep[]
-    if step < controller.stepmax[]
-        controller.timestep[] = step + 1
-    elseif isplaying(controller)
-        pause!(controller)
-    end
+function TimeController(player::AbstractPlayer)
+    stepmin = Observable(min(player))
+    stepmax = Observable(max(player))
+    TimeController(player, stepmin, stepmax, Observable(false))
 end
 
-function stepbackward!(controller::TimeController)
-    step = controller.timestep[]
-    if controller.stepmin[] < step
-        controller.timestep[] = step - 1
-    elseif isplaying(controller)
-        pause!(controller)
-    end
+Players.timestep(controller::TimeController) = timestep(controller.player)
+Players.isplaying(controller::TimeController) = isplaying(controller.player)
+Base.min(controller::TimeController) = controller.stepmin[]
+Base.max(controller::TimeController) = controller.stepmax[]
+Players.timestamps(controller::TimeController) = timestamps(controller.player)
+Players.speed(controller::TimeController) = Players.speed(controller.player)
+Players.repeat(controller::TimeController) = Players.repeat(controller.player)
+Players.Model.task(controller::TimeController) = Players.Model.task(controller.player)
+function Players.Model.task!(controller::TimeController, task)
+    Players.Model.task!(controller.player, task)
 end
 
 function timecontroller(times::Vector{Float64}; speed=1.0)
-    issorted(times) || throw(ArgumentError("timestamps are not sorted"))
-    initialstep = 1
-    timestep = newobservable(Cooldown(0.1), initialstep)
-    time = Observable(times[initialstep])
-    nsteps = length(times)
-    stepmin = Observable(1)
-    stepmax = Observable(nsteps)
-    boundreached = Observable(true)
-    initialized = Ref(false) # useful for skipping a "Time bound reached" message at startup
-    #
-    on(timestep) do step
-        min, max = stepmin[], stepmax[]
-        if step < min
-            timestep.val = min
-            @warn "step < stepmin"
-        elseif max < step
-            timestep.val = max
-            @warn "step > stepmax"
-        else
-            time[] = times[step]
-            if step == min || step == max
-                boundreached[] = true
-            elseif boundreached[]
-                boundreached[] = false
-            end
-        end
-    end
-    on(boundreached) do b
-        b && initialized[] && @info "Time bound reached"
-        initialized[] = true
-    end
-    on(stepmin) do bound
-        0 < bound || throw(DomainError("stepmin < 1"))
-        if timestep[] < bound
-            timestep[] = bound
-        end
-    end
-    on(stepmax) do bound
-        bound <= nsteps || throw(DomainError("stepmax > #steps=$(nsteps)"))
-        if bound < timestep[]
-            timestep[] = bound
-        end
-    end
-    #
-    playing = Observable(false)
-    if !(speed isa AbstractObservable)
-        speed = Observable(speed)
-    end
-    controller = TimeController(times, time, timestep,
-                                stepmin, stepmax, boundreached,
-                                playing, speed)
-    on(playing) do playnow
-        if playnow
-            @async playnow!(controller)
-        end
-    end
-    return controller
+    player = Player(times; speed=speed, loop=nothing)
+    ctrl = TimeController(player)
+    Players.Model.bind(ctrl, Players.Model.basicloop)
+    return ctrl
 end
 
 function slave(master::TimeController, policy::ObservationPolicy=IndependentObservables)
-    slave_timestep = newobservable(policy, master.timestep)
-    slave_time = map(slave_timestep) do step
-        master.times[step]
-    end
-    TimeController(master.times,
-                   slave_time,
-                   slave_timestep,
-                   master.stepmin,
-                   master.stepmax,
-                   master.boundreached,
-                   master.playing,
-                   master.speed)
-end
-
-isslave(controller::TimeController) = ObservationPolicies.haspolicy(controller.timestep)
-
-function stop!(controller::TimeController)
-    pause!(controller)
-    if isslave(controller)
-        # duck typing
-        try
-            ObservationPolicies.stop!(controller.timestep.policy)
-        catch
-            @debug begin
-                typ = typeof(controller.timestep.policy)
-                "`stop!` not implemented for slave controller $(typ)"
-            end
-        end
-    end
+    slave_timestep = newobservable(policy, timestep(master))
+    slave_player = Player(timestamps(master),
+                          slave_timestep,
+                          Players.speed(master),
+                          isplaying(master),
+                          Players.repeat(master),
+                          player.task)
+    TimeController(slave_player, master.stepmin, master.stepmax, master.boundreached)
 end
 
-gettimes(c::AbstractAnimator) = c.times
-gettimestep(c::AbstractAnimator) = c.timestep
-settimestep!(c::AbstractAnimator, t) = (c.timestep[] = t)
-
+isslave(controller::TimeController) = ObservationPolicies.haspolicy(timestep(controller))
+
+# function stop!(controller::TimeController)
+#     pause(controller)
+#     if isslave(controller)
+#         policy = timestep(controller).policy
+#         # duck typing
+#         try
+#             ObservationPolicies.stop!(policy)
+#         catch
+#             @debug "`stop!` not implemented for slave controller $(typeof(policy))"
+#         end
+#     end
+# end
+
+# aliases
+gettimes(c::AbstractPlayer) = timestamps(c)[]
+gettimestep(c::AbstractPlayer) = timestep(c)
+settimestep!(c::AbstractPlayer, t) = (timestep(c)[] = t)
 gettimes(c) = gettimes(getplayer(c))
 gettimestep(c) = gettimestep(getplayer(c))
 settimestep!(c, t) = settimestep!(getplayer(c), t)
@@ -256,6 +77,6 @@ function settimebounds!(c::TimeController, larva)
 end
 function unsettimebounds!(c::TimeController)
     c.stepmin[] = 1
-    c.stepmax[] = length(c.times)
+    c.stepmax[] = length(timestamps(c)[])
     @debug "Unbounding time" stepmin=c.stepmin[] stepmax=c.stepmax[]
 end
diff --git a/src/plots.jl b/src/plots.jl
index 104d856..4a76a07 100644
--- a/src/plots.jl
+++ b/src/plots.jl
@@ -25,8 +25,6 @@ withalpha(outline_color::Observable, alpha_value::Observable) = lift(withalpha,
 
 const MinimalLarva = Tuple{PathOrOutline, PathOrOutline, Color, Bool}
 
-const wgl = startswith(string(Makie.current_backend[]), "WGL")
-
 function Makie.plot!(plot::LarvaPlot{<:MinimalLarva})
     path = plot[1]
     shape_outline = plot[2]
@@ -41,13 +39,6 @@ function Makie.plot!(plot::LarvaPlot{<:MinimalLarva})
            linewidth=plot.shape_linewidth,
            visible=visibility)
     translate!(plot.plots[end], Makie.Vec3f(0, 0, 1))
-    if !wgl
-        poly!(plot, shape_outline;
-              color=withalpha(shape_color, plot.shape_alpha),
-              transparency=true,
-              visible=visibility)
-        translate!(plot.plots[end], Makie.Vec3f(0, 0, 0.5))
-    end
 end
 
 """
@@ -169,7 +160,6 @@ function SingleLarvaView(larvae::Vector{LarvaModel}, controller; editabletags::B
 
     tags = gettags(controller)
     timestep = gettimestep(controller)
-    player = getplayer(controller)
 
     f = on(usertags[]) do _
         _, color = gettag(tags[], model[], timestep[], fallback_color)
@@ -207,7 +197,6 @@ function SingleLarvaView(larvae::Vector{LarvaModel}, controller; editabletags::B
             shape_color.val = html_color(color)
             #
             visible[] = true # notify all subplots
-            timestep == last_timestep && pause!(player)
         elseif visible[]
             visible[] = false
         end
@@ -599,12 +588,19 @@ function DecoratedLarvae(larvae::Vector{DecoratedLarva})
             end
         end
     end
-    center = Meshes.coordinates ∘ Meshes.centroid
-    centers = cat((center(larva.activearea) for larva in larvae)...; dims=2)
-    norm2 = sum(centers .* centers, dims=1)
+    centers = zeros(Float32, (2, 0))
+    norm2 = zeros(Float32, (1, 0))
+    if ~isempty(larvae)
+        center = Meshes.coordinates ∘ Meshes.centroid
+        centers = cat((center(larva.activearea) for larva in larvae)...; dims=2)
+        norm2 = sum(centers .* centers, dims=1)
+    end
     DecoratedLarvae(larvae, centers, norm2, hovered_larva, hovering_active)
 end
 
+Base.isempty(larvae::DecoratedLarvae) = isempty(larvae.larvae)
+Base.length(larvae::DecoratedLarvae) = length(larvae.larvae)
+
 function find(larvae::DecoratedLarvae, position)
     pos = Vector(position)'
     p2 = pos * pos'
@@ -641,7 +637,13 @@ end
 
 Meshes.boundingbox(larva::StatefulLarva) = Meshes.boundingbox(larva.model)
 Meshes.boundingbox(larva::DecoratedLarva) = Meshes.boundingbox(larva.larva)
-Meshes.boundingbox(larvae::DecoratedLarvae) = Meshes.boundingbox(map(Meshes.boundingbox, larvae.larvae))
+function Meshes.boundingbox(larvae::DecoratedLarvae)
+    if isempty(larvae)
+        Meshes.Box(Meshes.Point(0.0, 1.0), Meshes.Point(0.0, 1.0))
+    else
+        Meshes.boundingbox(map(Meshes.boundingbox, larvae.larvae))
+    end
+end
 
 function setmouseevents!(scene, plot::DecoratedLarvae, ctrl; consume=false)
     on(events(scene).mousebutton) do mb
diff --git a/src/viewer.jl b/src/viewer.jl
index 7463492..b2344b4 100644
--- a/src/viewer.jl
+++ b/src/viewer.jl
@@ -1,51 +1,58 @@
 
 struct ViewerView
     assayplot::AssayViewer
-    trackplot::TrackViewer
+    trackplot::Union{Nothing, TrackViewer}
 end
 
-function JSServe.jsrender(session::Session, vv::ViewerView)
-    controller = vv.trackplot.plot.controller
+function Bonito.jsrender(session::Session, vv::ViewerView)
     delayed_controller = vv.assayplot.plot.controller
 
-    assaydom = r(session, vv.assayplot)
-    trackdom = r(session, vv.trackplot)
-
-    on(larvaevents(controller).activated) do _
-        vv.assayplot.visible[] = false
-        JSServe.evaljs(session,
-                       js"""
-                       const assayviewer = $(assaydom);
-                       const trackviewer = $(trackdom);
-                       assayviewer.style.display = "none";
-                       trackviewer.style.display = "block";
-                       """)
-    end
-    on(larvaevents(controller).deactivated) do _
-        vv.assayplot.visible[] = true
-        JSServe.evaljs(session,
-                       js"""
-                       const assayviewer = $(assaydom);
-                       const trackviewer = $(trackdom);
-                       trackviewer.style.display = "none";
-                       assayviewer.style.display = "block";
-                       """)
-    end
-
     on(session.on_close) do closed
         if closed
-            stop!(delayed_controller)
+            pause(delayed_controller.player)
         end
     end
 
-    r(session,
-      DOM.div(JSServe.TailwindCSS, assaydom, trackdom; class="flex flex-row"))
+    assaydom = r(session, vv.assayplot)
+    trackdom = nothing
+
+    if !isnothing(vv.trackplot)
+        controller = vv.trackplot.plot.controller
+        trackdom = r(session, vv.trackplot)
+
+        on(session, larvaevents(controller).activated) do _
+            vv.assayplot.visible[] = false
+            evaljs(session,
+                js"""
+                const assayviewer = $(assaydom);
+                const trackviewer = $(trackdom);
+                assayviewer.style.display = "none";
+                trackviewer.style.display = "block";
+                """)
+        end
+        on(session, larvaevents(controller).deactivated) do _
+            vv.assayplot.visible[] = true
+            evaljs(session,
+                js"""
+                const assayviewer = $(assaydom);
+                const trackviewer = $(trackdom);
+                trackviewer.style.display = "none";
+                assayviewer.style.display = "block";
+                """)
+        end
+
+        r(session,
+          DOM.div(Bonito.TailwindCSS, assaydom, trackdom; class="flex flex-row"))
+
+    else
+        r(session, DOM.div(Bonito.TailwindCSS, assaydom))
+    end
 end
 
 function larvaviewer(path::String; allow_multiple_tags::Union{Nothing, Bool}=false,
-    kwargs...)
+    title="LarvaTagger", kwargs...)
 
-    App() do session::Session
+    App(title=title) do session::Session
 
         controller = ControllerHub()
 
@@ -54,9 +61,12 @@ function larvaviewer(path::String; allow_multiple_tags::Union{Nothing, Bool}=fal
         viewer = larvaviewer(controller; multipletags=allow_multiple_tags, kwargs...)
 
         r(session, LarvaTaggerJS)
+
+        dom = Bonito.jsrender(session, viewer)
+
         r(session, LarvaTaggerCSS)
 
-        JSServe.jsrender(session, viewer)
+        return dom
     end
 end
 
@@ -65,9 +75,14 @@ function larvaviewer(controller;
         multipletags::Union{Nothing, Bool}=false,
         viewfactor::Real=1,
     )
-    model = getlarvae(controller)
-    times = gettimes(controller)
-    tag_lut = gettags(controller)
+    model = LarvaModel[]
+    times = PlanarLarvae.Time[0.0, 1.0]
+    tag_lut = Observable(TagLUT())
+    if haskey(controller, :larva)
+        model = getlarvae(controller)
+        times = gettimes(controller)
+        tag_lut = gettags(controller)
+    end
 
     controller[:player] = player = timecontroller(times)
     controller[:larva] = larva = LarvaController(controller, model, tag_lut, player)
diff --git a/src/wgl.jl b/src/wgl.jl
index 66408bb..c21d40f 100644
--- a/src/wgl.jl
+++ b/src/wgl.jl
@@ -1,28 +1,15 @@
 getsession(c) = gethub(c)[:session]
 setsession!(c, session) = (gethub(c)[:session] = session)
 
-const r = JSServe.jsrender
+const r = Bonito.jsrender
 
-const LarvaTaggerJS = JSServe.Dependency(:LarvaTagger,
-                                         [joinpath(@__DIR__, "larvatagger.js")])
-const LarvaTaggerCSS = JSServe.Asset(joinpath(@__DIR__, "larvatagger.css"))
-const LoadAwesomeCSS = JSServe.Asset(joinpath(@__DIR__, "loadawesome.css"))
+const LarvaTaggerJS = Bonito.Asset(joinpath(@__DIR__, "larvatagger.js"))
+const LarvaTaggerCSS = Bonito.Asset(joinpath(@__DIR__, "larvatagger.css"))
+const LoadAwesomeCSS = Bonito.Asset(joinpath(@__DIR__, "loadawesome.css"))
 
 scrollable(elements...) = DOM.div(DOM.table(elements...);
                                   class="scrollable", scrolling="auto")
 
-const Trigger = Observable{Bool}
-const Toggle = Observable{Bool}
-
-function simpletrigger(cls::DataType, args...)
-    click = Trigger()
-    on(click) do _
-        #click.val = false
-        @debug "Click on $cls"
-    end
-    return cls(click, args...)
-end
-
 function with_attributes(kwargs; class="", style="", kwargs′...)
     attributes = Dict{Symbol, Any}(kwargs′)
     merge!(attributes, Dict{Symbol, Any}(kwargs))
@@ -42,36 +29,19 @@ function with_attributes(kwargs; class="", style="", kwargs′...)
     return attributes
 end
 
-css(lines...) = join(lines, "; ")
-
 css_button = "button"
 
-function DOMtrigger(label, trigger, attributes)
-    DOM.button(label;
-               type="button",
-               onclick=js"""
-               JSServe.update_obs($trigger, true);
-               """,
-               attributes...
-              )
-end
-
-DOMtrigger(label, trigger; kwargs...) = DOMtrigger(label, trigger, with_attributes(kwargs; class=css_button))
-
-function DOMtoggle(label, jlstate, attributes)
-    DOM.button(label;
-               type="button",
-               onclick=js"LarvaTagger.toggle($jlstate)",
-               attributes...
-              )
-end
-
 struct ControlPanel
     id::String
     title::String
     active::Bool
     elements
     attributes
+    dom_id
+end
+
+function ControlPanel(id::String, title::String, active::Bool, elements, attributes)
+    ControlPanel(id, title, active, elements, attributes, dom_id())
 end
 
 function ControlPanel(elements...; id::String, title::String, active::Bool=false, attributes...)
@@ -79,34 +49,23 @@ function ControlPanel(elements...; id::String, title::String, active::Bool=false
     ControlPanel(id, title, active, elements, attributes)
 end
 
-function JSServe.jsrender(session::Session, panel::ControlPanel)
+function lowerdom(panel::ControlPanel)
     tabid = "select-" * panel.id
     escaped_attrs = (:for=>tabid,)
-    r(session,
-      DOM.div(DOM.input(; type="radio", name="tab", id=tabid, class="cp-tab-switch",
-                          checked=panel.active),
-              DOM.label(panel.title; class="cp-tab-label", escaped_attrs...),
-              DOM.div(panel.elements...; id=panel.id, panel.attributes...);
-              class="cp-tab"))
+    return DOM.div(DOM.input(; type="radio", name="tab", id=tabid, class="cp-tab-switch",
+                             checked=panel.active),
+                   DOM.label(panel.title; class="cp-tab-label", escaped_attrs...),
+                   DOM.div(panel.elements...; id=panel.id, panel.attributes...);
+                   class="cp-tab")
 end
+Bonito.jsrender(session::Session, panel::ControlPanel) = lowerdom(session, panel)
 
 cp(panels...) = DOM.div(panels...; class="cp-tabs")
 
-struct HomeButton
-    click::Trigger
-    attributes
-end
-
-function homebutton(kwargs...)
-    simpletrigger(HomeButton,
-                  with_attributes(kwargs;
-                                  class=css_button,
-                                  style="position:absolute; top:1.5625rem; right:2.5rem;"))
-end
-
-function JSServe.jsrender(session::Session, b::HomeButton)
-    r(session,
-      DOMtrigger("⌂", b.click, b.attributes))
+function homebutton()
+    NyxWidgets.Button("⌂";
+                      class=css_button,
+                      style="position:absolute; top:1.5625rem; right:2.5rem;")
 end
 
 struct AssayPlot
@@ -114,22 +73,30 @@ struct AssayPlot
     figure::Figure
     axis::Axis
     larvae::DecoratedLarvae
-    homebutton::HomeButton
+    homebutton::NyxWidgets.Button
+    dom_id
+end
+
+function AssayPlot(controller, figure::Figure, axis::Axis, larvae::DecoratedLarvae,
+        homebutton::NyxWidgets.Button)
+    AssayPlot(controller, figure, axis, larvae, homebutton, dom_id())
 end
 
 function AssayPlot(ctrl, larvae::DecoratedLarvae; size=FIGSIZE)
     gethub(ctrl)[:decoratedlarvae] = larvae
-    fig = Figure(resolution=size)
+    fig = Figure(size=size)
     width = 0.1f0 # try to get 1px
     color = RGBAf(0, 0, 0, 0.36)
     ax = Axis(fig.layout[1, 1], aspect=DataAspect(), xgridwidth=width, ygridwidth=width,
               xgridcolor=color, ygridcolor=color)
     autosize!(ax, size)
-    plot = larvaplot!(ax, larvae)
-    setkeyboardevents!(plot.parent, getplayer(ctrl))
-    setbounds!(ax, ctrl, larvae)
+    if !isempty(larvae)
+        plot = larvaplot!(ax, larvae)
+        setkeyboardevents!(plot.parent, getplayer(ctrl))
+        setbounds!(ax, ctrl, larvae)
+    end
     button = homebutton()
-    on(button.click) do _
+    on(button) do _
         setbounds!(ax, ctrl, larvae)
     end
     AssayPlot(ctrl, fig, ax, larvae, button)
@@ -162,12 +129,8 @@ function assayplot(data, ctrl, args...; kwargs...)
     return plot
 end
 
-function JSServe.jsrender(session::Session, p::AssayPlot)
-    r(session,
-      DOM.div(r(session, p.homebutton),
-              p.figure;
-              class="relative"))
-end
+lowerdom(p::AssayPlot) = DOM.div(p.homebutton, p.figure; class="relative")
+Bonito.jsrender(session::Session, p::AssayPlot) = lowerdom(session, p)
 
 function setmouseevents!(plot::AssayPlot; kwargs...)
     setmouseevents!(plot.axis.scene, plot.larvae, plot.controller; kwargs...)
@@ -179,13 +142,19 @@ struct TrackPlot
     axis::Axis
     larvae::Vector{LarvaModel}
     view::SingleLarvaView
-    homebutton::HomeButton
+    homebutton::NyxWidgets.Button
+    dom_id
+end
+
+function TrackPlot(controller, figure, axis::Axis, larvae::Vector{LarvaModel},
+        view::SingleLarvaView, homebutton::NyxWidgets.Button)
+    TrackPlot(controller, figure, axis, larvae, view, homebutton, dom_id())
 end
 
 function TrackPlot(controller, larvae::Vector{LarvaModel};
         size=FIGSIZE, editabletags::Bool=true)
     view = SingleLarvaView(larvae, controller; editabletags=editabletags)
-    fig = Figure(resolution=size)
+    fig = Figure(size=size)
     width = 0.1f0 # try to get 1px
     color = RGBAf(0, 0, 0, 0.36)
     ax = Axis(fig.layout[1, 1], aspect=DataAspect(), xgridwidth=width, ygridwidth=width,
@@ -205,7 +174,7 @@ function TrackPlot(controller, larvae::Vector{LarvaModel};
         end
     end
     button = homebutton()
-    on(button.click) do _
+    on(button) do _
         deactivatelarva!(controller)
     end
     TrackPlot(controller, fig, ax, larvae, view, button)
@@ -215,212 +184,38 @@ TrackPlot(ctrl, model::Dict{<:Integer, LarvaModel}, args...; kwargs...) = TrackP
 
 trackplot(model, controller; kwargs...) = TrackPlot(controller, model; kwargs...)
 
-function JSServe.jsrender(session::Session, p::TrackPlot)
-    # TODO: read client window size
-    r(session,
-      DOM.div(r(session, p.homebutton),
-              p.figure;
-              class="relative"))
-end
-
-struct PlayPauseButton
-    playing::AbstractObservable{Bool}
-    attributes::Dict{Symbol, Any}
-end
-
-function playpausebutton(ctrl::TimeController; kwargs...)
-    PlayPauseButton(ctrl.playing, with_attributes(kwargs; class=css_button))
-end
-
-function JSServe.jsrender(session::Session, b::PlayPauseButton)
-    r(session,
-      DOMtoggle("Play/Pause", b.playing, b.attributes))
-end
-
-struct StepForwardButton
-    click::Trigger
-    attributes::Dict{Symbol, Any}
-end
-
-function stepforwardbutton(ctrl::TimeController; kwargs...)
-    button = simpletrigger(StepForwardButton, with_attributes(kwargs; class=css_button))
-    on(button.click) do _
-        stepforward!(ctrl)
-    end
-    return button
-end
-
-function JSServe.jsrender(session::Session, b::StepForwardButton)
-    r(session,
-      DOMtrigger(">", b.click, b.attributes))
-end
-
-struct StepBackwardButton
-    click::Trigger
-    attributes::Dict{Symbol, Any}
-end
-
-function stepbackwardbutton(ctrl::TimeController; kwargs...)
-    button = simpletrigger(StepBackwardButton, with_attributes(kwargs; class=css_button))
-    on(button.click) do _
-        stepbackward!(ctrl)
-    end
-    return button
-end
-
-function JSServe.jsrender(session::Session, b::StepBackwardButton)
-    r(session,
-      DOMtrigger("<", b.click, b.attributes))
-end
-
-struct TimeSlider
-    master::TimeController
-    attributes::Dict{Symbol, Any}
-end
-
-function timeslider(master::TimeController; kwargs...)
-    TimeSlider(master,
-               with_attributes(kwargs;
-                               style="width: 100%",
-                               class="ml-1 mr-1"))
-end
-timeslider(times::Vector{Float64}; kwargs...) = timeslider(timecontroller(times); kwargs...)
-
-function JSServe.jsrender(session::Session, ts::TimeSlider)
-    master = ts.master
-    input = ObservationPolicies.input(master.timestep)
-    output = ObservationPolicies.output(master.timestep)
-    moveslider = js"LarvaTagger.timeSlider(this, $input, $(master.stepmin), $(master.stepmax)).moveto(value)"
-    slider = DOM.input(type="range",
-                       min=1,
-                       max=length(master.times),
-                       value=output,
-                       step=1,
-                       oninput=moveslider;
-                       ts.attributes...)
-    JSServe.onload(session, slider, js"(slider)=>{ slider.value = 1; }")
-    r(session, slider)
-end
-
-struct TimeLabel
-    time::AbstractObservable{Float64}
-    ndecimals::Int
-    attributes::Dict{Symbol, Any}
-end
-
-function timelabel(time::AbstractObservable{Float64};
-                   ndecimals=2, kwargs...)
-    TimeLabel(time,
-              ndecimals,
-              with_attributes(kwargs,
-                              style=css("width: 12.5rem",
-                                        "display: flex",
-                                        "flex-direction: row",
-                                        "align-items: center")))
-end
-timelabel(master::TimeController; kwargs...) = timelabel(master.time; kwargs...)
-
-function JSServe.jsrender(session::Session, tl::TimeLabel)
-    fmt = FormatExpr("t={: .$(tl.ndecimals)f} s")
-    formatter = t -> format(fmt, t)
-    r(session,
-      DOM.label(lift(formatter, tl.time);
-                tl.attributes...))
-end
-
-struct PlaybackSpeedList
-    backspeed::AbstractObservable{Float64}
-    frontspeed::AbstractObservable{Float64}
-    attributes::Dict{Symbol, Any}
-end
-
-function playbackspeedlist(animator; kwargs...)
-    backspeed = animator.speed
-    frontspeed = map(a -> a, backspeed)
-    PlaybackSpeedList(backspeed, frontspeed, with_attributes(kwargs; class="speed"))
-end
-
-function JSServe.jsrender(session::Session, sl::PlaybackSpeedList)
-    dom = r(session,
-            DOM.select(DOM.option("0.25"; value=0.25),
-                       DOM.option("0.5"; value=0.5),
-                       DOM.option("0.75"; value=0.75),
-                       DOM.option("1.0"; value=1.0),
-                       DOM.option("1.25"; value=1.25),
-                       DOM.option("1.5"; value=1.5),
-                       DOM.option("2.0"; value=2.0);
-                       onchange=js"JSServe.update_obs($(sl.backspeed), parseFloat(this.value))",
-                       sl.attributes...))
-    on(sl.frontspeed) do speed
-        frontspeed = if speed < 0.375
-            "0.25"
-        elseif speed < 0.625
-            "0.5"
-        elseif speed < 0.875
-            "0.75"
-        elseif speed < 1.125
-            "1.0"
-        elseif speed < 1.375
-            "1.25"
-        elseif speed < 1.75
-            "1.5"
-        else
-            "2.0"
-        end
-        evaljs(session, js"$(dom).value = $frontspeed;")
-    end
-    notify(sl.frontspeed)
-    return dom
-end
-
-struct Player
-    controller::TimeController
-    timeslider::TimeSlider
-    timelabel::TimeLabel
-    playpausebutton::PlayPauseButton
-    stepbackwardbutton::StepBackwardButton
-    stepforwardbutton::StepForwardButton
-    playbackspeedlist::PlaybackSpeedList
-    attributes::Dict{Symbol, Any}
-end
-
+lowerdom(p::TrackPlot) = DOM.div(p.homebutton, p.figure; class="relative")
+Bonito.jsrender(session::Session, p::TrackPlot) = lowerdom(session, p)
 
 player(times::Vector; kwargs...) = player(timecontroller(times); kwargs...)
 
 player(controller; kwargs...) = player(getplayer(controller); kwargs...)
 
 function player(animator::TimeController; kwargs...)
-    Player(animator,
-           timeslider(animator),
-           timelabel(animator),
-           playpausebutton(animator),
-           stepbackwardbutton(animator),
-           stepforwardbutton(animator),
-           playbackspeedlist(animator),
-           with_attributes(kwargs;
-                           class="flex flex-row player",
-                           style="width: 100%; height: 2.5rem"),
-          )
-end
-
-function JSServe.jsrender(session::Session, p::Player)
-    r(session,
-      DOM.div(r(session, p.playpausebutton),
-              r(session, p.stepbackwardbutton),
-              r(session, p.stepforwardbutton),
-              r(session, p.playbackspeedlist),
-              r(session, p.timeslider),
-              r(session, p.timelabel);
-              onmouseenter=js"LarvaTagger.focusOnTimeSlider(this)",
-              p.attributes...
-             ))
-end
-
-mutable struct AssayViewer
+    step = timestep(animator)
+    stepmin = animator.stepmin
+    stepmax = animator.stepmax
+    playbackspeeds = ["0.25", "0.5", "0.75", "1.0", "1.25", "1.5", "2"]
+    view = PlayerView(animator, playbackspeeds)
+    oninput = js"""(evt) => {
+        var step = evt.srcElement.value;
+        LarvaTagger.timeSlider(evt.srcElement, $step, $stepmin, $stepmax).moveto(step)
+    }"""
+    # set oninput attribute in immutable structs
+    slider = view.timeslider
+    slider = Players.TimeSlider(slider.player, slider.index, slider.playpause, oninput,
+                                slider.attributes, view.dom_id)
+    view = PlayerView(view.player, view.playpause, view.stepforward, view.stepbackward,
+                      view.repeat, view.speedselector, slider, view.timelabel,
+                      view.attributes, view.dom_id)
+    return view
+end
+
+struct AssayViewer
     plot::AssayPlot
-    player::Player
+    player::PlayerView
     visible::AbstractObservable{Bool}
-    dom
+    dom_id
 end
 
 function assayviewer(controller; kwargs...)
@@ -434,18 +229,14 @@ function assayviewer(controller; kwargs...)
             notify(gettimestep(controller))
         end
     end
-    AssayViewer(plot, play, visible, nothing)
+    AssayViewer(plot, play, visible, dom_id())
 end
 
-function JSServe.jsrender(session::Session, av::AssayViewer)
-    dom = DOM.div(r(session, av.plot),
-                  r(session, av.player);
-                  class="flex flex-col",
-                  onmouseenter=js"LarvaTagger.focusOnTimeSlider(this)",
-                 )
-    av.dom = dom
-    return r(session, dom)
+function lowerdom(av::AssayViewer)
+    focus = js"(evt)=>{ LarvaTagger.focusOnTimeSlider(evt.srcElement); }"
+    return DOM.div(av.plot, av.player; class="flex flex-col", onmouseenter=focus)
 end
+Bonito.jsrender(session::Session, av::AssayViewer) = lowerdom(session, av)
 
 struct LarvaInfo
     controller
@@ -455,6 +246,7 @@ struct LarvaInfo
     reviewed::AbstractObservable{Bool}
     edited::AbstractObservable{Bool}
     included::AbstractObservable{Bool}
+    dom_id
 end
 
 function larvainfo(controller, id)
@@ -516,28 +308,30 @@ function larvainfo(controller, id)
               clicked,
               reviewed,
               edited,
-              included)
+              included,
+              dom_id())
 end
 
-JSServe.jsrender(session::Session, li::LarvaInfo) = r(session, prerender(li))
+Bonito.jsrender(session::Session, li::LarvaInfo) = lowerdom(session, li)
 
-function prerender(li::LarvaInfo)
+function lowerdom(li::LarvaInfo)
     label = "#$(li.id)"
-    discard_larva_edits = js"LarvaTagger.discardLarvaEdits(this, $(li.edited), $label)"
     label = DOM.label(label,
-                      onmouseenter=js"JSServe.update_obs($(li.hovered), true)",
-                      onmouseleave=js"JSServe.update_obs($(li.hovered), false)",
-                      onmouseup=js"JSServe.update_obs($(li.clicked), true)")
+                      onmouseenter=js"()=>{ $(li.hovered).notify(true); }",
+                      onmouseleave=js"()=>{ $(li.hovered).notify(false); }",
+                      onmouseup=js"()=>{ $(li.clicked).notify(true); }")
+    toggle_reviewed = js"(evt)=>{ $(li.reviewed).notify(evt.srcElement.checked); }"
     reviewed_checkbox = DOM.input(type="checkbox",
                                   checked=li.reviewed,
-                                  onchange=js"""
-                                  JSServe.update_obs($(li.reviewed), this.checked);
-                                  """)
+                                  onchange=toggle_reviewed)
+    discard_larva_edits = js"""(evt)=>{
+        LarvaTagger.discardLarvaEdits(evt.srcElement, $(li.edited), $label);
+    }"""
     edited_checkbox = DOM.input(type="checkbox",
                                 checked=li.edited,
                                 # onchange is not triggered from Julia
                                 onchange=discard_larva_edits)
-    include = js"LarvaTagger.includeLarva(this, $(li.included))"
+    include = js"(evt)=>{ LarvaTagger.includeLarva(evt.srcElement, $(li.included)); }"
     included_checkbox = DOM.input(type="checkbox",
                                   checked=li.included,
                                   class="included",
@@ -554,6 +348,12 @@ struct LarvaFilter
     includeall::AbstractObservable{Bool}
     saveas::AbstractObservable{Bool}
     attributes
+    dom_id
+end
+
+function LarvaFilter(controller, entries::Vector{LarvaInfo},
+        includeall::AbstractObservable{Bool}, saveas::AbstractObservable{Bool}, attributes)
+    LarvaFilter(controller, entries, includeall, saveas, attributes, dom_id())
 end
 
 function larvafilter(controller::ControllerHub; kwargs...)
@@ -586,18 +386,21 @@ end
 
 getlarvafilter(controller) = gethub(controller)[:larvafilter]
 
-function JSServe.jsrender(session::Session, lf::LarvaFilter)
-    outputfile = getoutputfile(lf.controller)
+
+function lowerdom(lf::LarvaFilter)
+    outputfilename = getoutputfile(lf.controller).name
     disabled = map(!, lf.saveas)
-    button = r(session,
-               DOM.button("Save as...";
-                          type="button",
-                          id="saveas",
-                          onclick=js"LarvaTagger.setOutputFilename($(outputfile.name))",
-                          disabled=disabled,
-                          style="width: 100%",
-                          class=css_button))
-    includeall = js"LarvaTagger.includeAllLarvae(this, $(lf.includeall), $button)"
+    setoutputfilename = js"()=>{ LarvaTagger.setOutputFilename($outputfilename); }"
+    button = DOM.button("Save as...";
+                        type="button",
+                        id="saveas",
+                        onclick=setoutputfilename,
+                        disabled=disabled,
+                        style="width: 100%",
+                        class=css_button)
+    includeall = js"""(evt)=>{
+        LarvaTagger.includeAllLarvae(evt.srcElement, $(lf.includeall), $button);
+    }"""
     table = scrollable(DOM.tr(DOM.th(""),
                               DOM.th("Reviewed"),
                               DOM.th("Edited"),
@@ -610,15 +413,19 @@ function JSServe.jsrender(session::Session, lf::LarvaFilter)
                                                id="includeall",
                                                onchange=includeall),
                                      style="text-align: center;")),
-                       prerender.(lf.entries)...)
-    JSServe.onload(session, table, js"LarvaTagger.uncheckAllCheckboxes")
-    r(session,
-      DOM.div(table,
-              button;
-              with_attributes(lf.attributes; class="panel")...))
+                       lowerdom.(lf.entries)...)
+    return DOM.div(table,
+                   button;
+                   with_attributes(lf.attributes; class="panel")...)
 end
 
-abstract type AbstractView end
+function Bonito.jsrender(session::Session, lf::LarvaFilter)
+    node = lowerdom(session, lf)
+    Bonito.onload(session, node, js"""(parent)=>{
+        LarvaTagger.uncheckAllCheckboxes(parent.firstElementChild);
+    }""")
+    return node
+end
 
 mutable struct TagController
     hub::ControllerHub
@@ -646,32 +453,21 @@ end
 
 getmanualtag(c) = gethub(c)[:tag].manualtag
 
-function setcallbacks!(controller, tag, checkbox, label, colorpicker)
-    session = getsession(controller)
-    JSServe.attribute_render(session, label, "value", tag.name)
-    JSServe.attribute_render(session, colorpicker, "value", tag.color)
-    #
-    on(tagevents(controller).renamefailed) do (name, failure)
-        name == tag.name[] || return
-        @info "Reverting to \"$(name)\" after failure: \"$(failure)\""
-        evaljs(session,
-               js"""
-               const label = $(JSServe.selector(label));
-               label.value = $(name);
-               """)
-    end
-end
-
 newtagname(::TagController) = NoTag
 
 struct IndividualTagView
     controller::TagController
     tag::ObservableTag # view
+    dom_id
+end
+
+function IndividualTagView(controller::TagController, tag::ObservableTag)
+    IndividualTagView(controller, tag, dom_id())
 end
 
 tagview(controller, tag) = IndividualTagView(controller, tag)
 
-function JSServe.jsrender(session::Session, ti::IndividualTagView)
+function lowerdom(ti::IndividualTagView)
     tag = ti.tag
     active = tag.active
     disabled = map(!, active)
@@ -680,73 +476,63 @@ function JSServe.jsrender(session::Session, ti::IndividualTagView)
     #
     label = DOM.input(type="text",
                       value=name[],
-                      onchange=js"""
-                      JSServe.update_obs($(name), this.value);
-                      """,
+                      onchange=js"(evt)=>{ $name.notify(evt.srcElement.value); }",
                       disabled=disabled,
-                      class="tag-name",
-                     )
+                      class="tag-name")
     colorpicker = DOM.input(type="color",
                             value=color[],
-                            onchange=js"""
-                            JSServe.update_obs($(color), this.value);
-                            """,
+                            onchange=js"(evt)=>{ $color.notify(evt.srcElement.value); }",
                             disabled=disabled,
-                            class="h-8 w-8 rounded shadow tag-color",
-                           )
+                            class="h-8 w-8 rounded shadow tag-color")
     checkbox = DOM.input(type="checkbox",
                          checked=active,
-                         onchange=js"""
-                         JSServe.update_obs($(active), this.checked);
-                         """,
+                         onchange=js"(evt)=>{ $active.notify(evt.srcElement.checked); }",
                          class="tag-active")
-    setcallbacks!(ti.controller, tag, checkbox, label, colorpicker)
-    notify(active)
-    r(session,
-      DOM.tr(DOM.td(checkbox),
-             DOM.td(label),
-             DOM.td(colorpicker),
-            ))
+    return DOM.tr(DOM.td(checkbox),
+                  DOM.td(label),
+                  DOM.td(colorpicker))
+end
+function Bonito.jsrender(session::Session, ti::IndividualTagView)
+    node = lowerdom(session, ti)
+    tag = ti.tag
+    selector = dom_selector(ti) * " .tag-name"
+    events = tagevents(ti.controller)
+    on(events.renamefailed) do (name, failure)
+        name == tag.name[] || return
+        @info "Reverting to \"$(name)\" after failure: \"$(failure)\""
+        evaljs(session, js"document.querySelector($selector).value = $name;")
+    end
+    notify(tag.active)
+    return node
 end
 
 function addtag! end
 
-mutable struct AddTagButton
-    click::Trigger
-    attributes::Dict{Symbol, Any}
-end
-
-function addtagbutton(controller; kwargs...)
-    button = simpletrigger(AddTagButton,
-                           with_attributes(kwargs;
-                                           class=css_button,
-                                           style="width: 100%; height: 2rem; padding: 0px",
-                                          ))
-    on(button.click) do _
+function addtagbutton(controller)
+    button = NyxWidgets.Button(" + ";
+                               title="New tag",
+                               class=css_button,
+                               style="width: 100%; height: 2rem; padding: 0px")
+    on(button) do _
         addtag!(controller)
     end
     return button
 end
 
-function JSServe.jsrender(session::Session, b::AddTagButton)
-    r(session, DOMtrigger(" + ", b.click, b.attributes))
-end
-
-mutable struct TagFilter
+struct TagFilter
     controller::TagController
     views::Vector{IndividualTagView}
-    addtagbutton::AddTagButton
-    domtable::Union{Nothing, DOM.Hyperscript.Node{DOM.Hyperscript.HTMLSVG}}
+    addtagbutton::NyxWidgets.Button
     attributes
+    dom_id
 end
 
 function TagFilter(controller::TagController; kwargs...)
     TagFilter(controller,
               IndividualTagView[],
               addtagbutton(controller),
-              nothing,
               kwargs,
-             )
+              dom_id())
 end
 
 function tagfilter(controller; manualtag=nothing)
@@ -755,19 +541,18 @@ function tagfilter(controller; manualtag=nothing)
     return tc.view
 end
 
-function JSServe.jsrender(session::Session, tf::TagFilter)
-    setsession!(tf.controller, session)
+function lowerdom(tf::TagFilter)
     tags = newview!(gethub(tf.controller)[:taghooks])
     for tag in reverse(tags)
-        push!(tf.views,
-              tagview(tf.controller,
-                      tag))
+        push!(tf.views, tagview(tf.controller, tag))
     end
-    tf.domtable = r(session, scrollable(tf.views...))
-    r(session,
-      DOM.div(tf.domtable,
-              tf.addtagbutton;
-              with_attributes(tf.attributes; class="panel")...))
+    return DOM.div(scrollable(tf.views...),
+                   tf.addtagbutton;
+                   with_attributes(tf.attributes; class="panel")...)
+end
+function Bonito.jsrender(session::Session, tf::TagFilter)
+    setsession!(tf.controller, session)
+    return lowerdom(session, tf)
 end
 
 function addtag!(parent::TagController)
@@ -782,19 +567,31 @@ function addtag!(parent::TagController)
     #
     # model ordering: highest priority is first, lowest is last
     view = parent.view
-    childview = tagview(parent,
-                        newview!(tag))
+    childview = tagview(parent, newview!(tag))
     addtag!(view, childview)
     notify(taglut)
 end
 
 function addtag!(view::TagFilter, newtag::IndividualTagView)
     session = getsession(view.controller)
-    js_dom = r(session, newtag)
-    html_dom = "<table><tbody>" * replace(string(js_dom), "'" => "\\'") * "</tbody></table>"
-    evaljs(session, js"LarvaTagger.insertNewTag($(view.domtable), $html_dom)")
+    tag = newtag.tag
+    id0 = dom_id(session)
+    @assert id0 isa Int
+    tagactive = dom_selector(id0+3)
+    tagname = dom_selector(id0+5)
+    tagcolor = dom_selector(id0+7)
+    jsdom = r(session, newtag)
+    htmldom = "<table><tbody>" * replace(string(jsdom), "'" => "\\'") * "</tbody></table>"
+    domtable = dom_selector(view)
+    evaljs(session, js"""
+    let domtable = document.querySelector($domtable).firstElementChild;
+    LarvaTagger.insertNewTag(domtable, $htmldom);
+    document.querySelector($tagactive).addEventListener('change', (evt)=>{ $(tag.active).notify(evt.srcElement.checked); });
+    document.querySelector($tagname).addEventListener('change', (evt)=>{ $(tag.name).notify(evt.srcElement.value); });
+    document.querySelector($tagcolor).addEventListener('change', (evt)=>{ $(tag.color).notify(evt.srcElement.value); });
+    """)
     push!(view.views, newtag)
-    return js_dom
+    return jsdom
 end
 
 struct TagSelector
@@ -803,6 +600,8 @@ struct TagSelector
     selected::Observable{Vector{Tuple{String, Observable{Bool}}}}
     selectable::Bool
     showtoggle::Bool
+    fromjs::Union{Nothing, Observable{String}}
+    dom_id
 end
 
 function TagSelector(controller::TagController,
@@ -821,11 +620,27 @@ function TagSelector(controller::TagController,
     if !isnothing(multiple)
         multitag[] = multiple
     end
+    if selectable
+        fromjs = Observable("")
+        on(fromjs) do actuatedtag
+            for (tag, selected) in selectedtags[]
+                if tag == actuatedtag
+                    toggletag!(controller, tag, selected, selectedtags, selectable)
+                    flag_active_larva_as_edited(controller)
+                    break
+                end
+            end
+        end
+    else
+        fromjs = nothing
+    end
     ctrl = TagSelector(controller,
                        tagnames,
                        selectedtags,
                        selectable,
-                       isnothing(multiple))
+                       isnothing(multiple),
+                       fromjs,
+                       dom_id())
     refresh_view = Observable(false)
     on(tagnames) do names
         for i in length(registered_observables)+1:length(names)
@@ -888,7 +703,7 @@ function refresh_selected_tags(controller, id, timestep, taglut, selectedtags, s
     end
     isempty(jscode) && return
     evaljs(getsession(controller),
-           JSServe.JSCode([JSServe.JSString(join(jscode, "\n"))]))
+           Bonito.JSCode([Bonito.JSString(join(jscode, "\n"))]))
 end
 
 function refresh_selected_tag(controller, id, timestep, taglut, selectedtags, selectable)
@@ -922,7 +737,7 @@ function refresh_selected_tag(controller,
     end
     isempty(jscode) && return
     evaljs(getsession(controller),
-           JSServe.JSCode([JSServe.JSString(join(jscode, "\n"))]))
+           Bonito.JSCode([Bonito.JSString(join(jscode, "\n"))]))
 end
 
 function toggletag!(controller, tagname, selected, selectedtags, selectable)
@@ -957,62 +772,58 @@ function toggletag!(controller, tagname, selected, selectedtags, selectable)
     notify(larva.usertags)
 end
 
-function to_dom(selectedtags, selectable)
+function lowertags(selectedtags, selectable)
+    toggle = selectable ? js"LarvaTagger.toggleTagAtPointer" : nothing
     DOM.select(DOM.option(tagname;
                           id=tagname,
                           value=tagname,
                           selected=selected[],
-                          onmousedown=selectable ? js"LarvaTagger.toggleTagAtPointer(event)" : nothing,
-                         ) for (tagname, selected) in selectedtags;
+                          onmousedown=toggle)
+               for (tagname, selected) in selectedtags;
                multiple=true,
-               size=1,
-              )
+               size=1)
 end
 
-function JSServe.jsrender(session::Session, ts::TagSelector)
-    setsession!(ts.controller, session)
-    if ts.selectable
-        controller = ts.controller
-        selectedtags = ts.selected
-        fromjs = Observable("")
-        on(fromjs) do actuatedtag
-            for (tag, selected) in selectedtags[]
-                if tag == actuatedtag
-                    toggletag!(controller, tag, selected, selectedtags, ts.selectable)
-                    flag_active_larva_as_edited(controller)
-                    break
-                end
-            end
-        end
-        evaljs(session, js"LarvaTagger.setTagSelector($fromjs)")
-    end
-    taglist = r(session, DOM.div(to_dom(ts.selected[], ts.selectable); id="tag-selector"))
-    on(ts.selected) do selected
-        js_dom = JSServe.jsrender(session, to_dom(selected, ts.selectable))
-        html_dom = replace(string(js_dom), "'" => "\\'")
-        evaljs(session,
-               js"""
-               const select = $(taglist);
-               select.innerHTML = $html_dom;
-               """)
-    end
+function lowerdom(ts::TagSelector)
+    taglist = DOM.div(lowertags(ts.selected[], ts.selectable); id="tag-selector")
     elements = [taglist]
     if ts.showtoggle
         multitag = filterevents(ts.controller).allow_multiple_tags
+        toggle_multitag = js"(evt)=>{ $multitag.notify(evt.srcElement.checked); }"
         checkbox = DOM.div(DOM.input(type="checkbox",
                                      checked=multitag,
-                                     onchange=js"""
-                                     JSServe.update_obs($(multitag), this.checked);
-                                     """,
+                                     onchange=toggle_multitag,
                                      class="m-1"),
                            DOM.label("Multiple tags per time step");
                            class="flex flex-row")
         push!(elements, checkbox)
     end
-    JSServe.jsrender(session,
-                     DOM.div(elements...;
-                             class="flex flex-col",
-                             onmouseenter=js"LarvaTagger.focusOnTimeSlider()"))
+    return DOM.div(elements...;
+                   class="flex flex-col",
+                   onmouseenter=js"LarvaTagger.focusOnTimeSlider")
+end
+function Bonito.jsrender(session::Session, ts::TagSelector)
+    setsession!(ts.controller, session)
+    node = lowerdom(session, ts)
+    if ts.selectable
+        evaljs(session, js"LarvaTagger.setTagSelector($(ts.fromjs))")
+    end
+    selectedtags = ts.selected
+    prevtags = Ref(empty(selectedtags[]))
+    on(session, selectedtags) do selected
+        selected == prevtags[] && return
+        prevtags[] = selected
+        jsdom = r(session, lowertags(selected, ts.selectable))
+        htmldom = replace(string(jsdom), "'" => "\\'")
+        evaljs(session, js"""
+        document.getElementById('tag-selector').innerHTML = $htmldom;
+        let tags = document.querySelectorAll('#tag-selector option');
+        for (let i = 0; i < tags.length; i++) {
+            tags[i].addEventListener('mousedown', LarvaTagger.toggleTagAtPointer);
+        }
+        """)
+    end
+    return node
 end
 
 function tagselector(controller;
@@ -1025,9 +836,9 @@ end
 
 mutable struct TrackViewer
     plot::TrackPlot
-    player::Player
+    player::PlayerView
     tagselector::TagSelector
-    dom
+    dom_id
 end
 
 function trackviewer(controller;
@@ -1036,102 +847,84 @@ function trackviewer(controller;
         kwargs...
     )
     model = getlarvae(controller)
+    isempty(model) && return nothing
     plot = trackplot(model, controller; editabletags=editabletags, kwargs...)
     play = player(controller)
     sele = tagselector(controller; editabletags=editabletags, multipletags=multipletags)
-    TrackViewer(plot, play, sele, nothing)
+    TrackViewer(plot, play, sele, dom_id())
 end
 
-function JSServe.jsrender(session::Session, tv::TrackViewer)
-    dom = DOM.div(r(session, tv.plot),
-                  r(session, tv.player),
-                  r(session, tv.tagselector);
-                  class="flex flex-col",
-                  style="display: none;",
-                  id="trackviewer",
-                  onmouseenter=js"LarvaTagger.focusOnTimeSlider(this)",
-                 )
-    tv.dom = dom
-    r(session, dom)
+function lowerdom(tv::TrackViewer)
+    focus = js"(evt)=>{ LarvaTagger.focusOnTimeSlider(evt.srcElement); }"
+    return DOM.div(tv.plot,
+                   tv.player,
+                   tv.tagselector;
+                   class="flex flex-col",
+                   style="display: none;",
+                   id="trackviewer",
+                   onmouseenter=focus)
 end
+Bonito.jsrender(session::Session, tv::TrackViewer) = lowerdom(session, tv)
 
 struct MetadataEditor
     controller
     max_entries::Int
+    dom_id
 end
 
-MetadataEditor(controller) = MetadataEditor(controller, 10)
+MetadataEditor(controller) = MetadataEditor(controller, 10, dom_id())
 
 metadataeditor(controller) = MetadataEditor(controller)
 
-function JSServe.jsrender(session::Session, me::MetadataEditor)
+function lowerdom(me::MetadataEditor)
     table = getmetadatatable(me.controller, me.max_entries)
-    dom = DOM.div(DOM.div(DOM.input(value=fieldname,
-                                    type="text",
-                                    class="headerfield",
-                                    onchange=js"""
-                                    JSServe.update_obs($(fieldname), this.value);
-                                    """),
-                          DOM.input(value=fieldvalue,
-                                    type="text",
-                                    class="valuefield",
-                                    onchange=js"""
-                                    JSServe.update_obs($(fieldvalue), this.value);
-                                    """);
-                          class="flex flex-row")
-                  for (fieldname, fieldvalue) in pairs(table);
-                  id="metadata-panel",
-                  class="flex flex-col panel")
-    r(session, dom)
+    return DOM.div(DOM.div(DOM.input(value=fieldname,
+                                     type="text",
+                                     class="headerfield",
+                                     onchange=js"""(evt)=>{
+                                        $fieldname.notify(evt.srcElement.value);
+                                     }"""),
+                           DOM.input(value=fieldvalue,
+                                     type="text",
+                                     class="valuefield",
+                                     onchange=js"""(evt)=>{
+                                        $fieldvalue.notify(evt.srcElement.value);
+                                     }""");
+                           class="flex flex-row")
+                   for (fieldname, fieldvalue) in pairs(table);
+                   id="metadata-panel",
+                   class="flex flex-col panel")
 end
+Bonito.jsrender(session::Session, me::MetadataEditor) = lowerdom(session, me)
 
 struct FileMenu
     controller::ControllerHub
-    menuselection::Observable{String}
-    attributes
+    browser::FileBrowser
 end
 
 function filemenu(controller; kwargs...)
-    selection = Observable("")
-    menu = FileMenu(gethub(controller),
-                    selection,
-                    with_attributes(kwargs; size=5))
     wd = getworkingdir(controller)
-    on(selection) do file
-        if '/' in file
-            @logmsg SecurityAlert "'/' character found in file name" file client=identifyclient(controller)
-        elseif !isempty(file)
-            cwd(wd, file)
-        end
+    dir = joinpath(wd.root, wd.path[])
+    browser = FileBrowser(dir; root=wd.root, upload_button=true)
+    on(FileBrowsers.selectedfile(browser)) do file
+        tryopenfile(controller, file; reload=true)
     end
-    return menu
-end
-
-function JSServe.jsrender(session::Session, fm::FileMenu)
-    wdcontent = getwdcontent(fm.controller)
-    inputfile = getinputfile(fm.controller)
-    select = r(session,
-               DOM.select(multiple=true;
-                          with_attributes(fm.attributes; class="directory-content")...))
-    onjs(session, wdcontent, js"(directory_entries) =>
-         LarvaTagger.updateSelectOptions($select, directory_entries, $(fm.menuselection))")
-    wd = map(getpath(fm.controller)) do path
-        @assert !isempty(path)
-        if isempty(path) || path == "."
-            "."
-        else
-            parts = splitpath(path)
-            length(parts) == 1 ? "./" * parts[1] : ".../" * parts[end]
-        end
+    workingdir = FileBrowsers.workingdir(browser)
+    on(workingdir) do _
+        dir = workingdir[]
+        # maintain the deprecated WorkingDirectory as it may still be in use elsewhere
+        wd.path[] = dir
+        wd.content[] = [d for (_, d) in browser.model.content[]]
+    end
+    on(FilePickers.uploadedfile(browser)) do fileinfo
+        filepath = joinpath(workingdir[], fileinfo["name"])
+        saveinputfile(filepath, fileinfo["content"])
     end
-    dom = DOM.div(DOM.input(value=wd,
-                            type="text",
-                            readonly=true),
-                  select;
-                  id="filemenu-panel",
-                  class="flex flex-col panel")
-    notify(wdcontent) # populate the select element
-    r(session, dom)
+    return FileMenu(gethub(controller), browser)
+end
+
+function Bonito.jsrender(session::Session, fm::FileMenu)
+    r(session, DOM.div(fm.browser; class="panel"))
 end
 
 function globalrefresh(::Type{Session}, controller)
@@ -1161,41 +954,54 @@ end
 
 struct BackendMenu
     backends::Backends
-    send2backend::Observable
+    send2backend::NyxWidgets.Button
+    dom_id
 end
 
 function backendmenu(controller, location)
     backends = getbackends(controller, location)
-    send2backend = Observable(false)
+    disabled = map(isempty, backends.model_instances)
+    send2backend = NyxWidgets.Button("Autotag"; disabled=disabled)
     on(send2backend) do _
         predict(backends, controller[:input][])
     end
-    BackendMenu(backends, send2backend)
+    BackendMenu(backends, send2backend, dom_id())
 end
 
-function JSServe.jsrender(session::Session, bs::BackendMenu)
+function lowerdom(bs::BackendMenu)
     models = bs.backends.model_instances
+    model_instance = bs.backends.model_instance
+    update_model_instance = js"(evt)=>{ $model_instance.notify(evt.srcElement.value); }"
     select_dom = DOM.select(DOM.option(model; value=model) for model in models[];
-                         onchange=js"JSServe.update_obs($(bs.backends.model_instance), this.value)",
-                         class=css_button)
-    onjs(session, models, js"(options) => LarvaTagger.updateSelectOptions($select_dom, options)")
-    disabled = map(isempty, models)
-    dom = DOM.div(DOM.label("Backend"),
-                  DOM.select(DOM.option(backend; value=backend) for backend in bs.backends.backends;
-                             onchange=js"JSServe.update_obs($(bs.backends.active_backend), this.value)",
-                             class=css_button),
-                  DOM.label("Model instance"),
-                  select_dom,
-                  DOMtrigger("Autotag", bs.send2backend; disabled=disabled);
-                  class="flex flex-col panel")
-    r(session, dom)
+                            onchange=update_model_instance,
+                            class=css_button)
+    active_backend = bs.backends.active_backend
+    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;
+                              onchange=update_active_backend,
+                              class=css_button),
+                   DOM.label("Model instance"),
+                   select_dom,
+                   bs.send2backend;
+                   class="flex flex-col panel")
+end
+function Bonito.jsrender(session::Session, bs::BackendMenu)
+    node = lowerdom(session, bs)
+    onjs(session, bs.backends.model_instances, js"""(options) => {
+         select = document.querySelector($(dom_selector(bs)) + ' > select');
+         LarvaTagger.updateSelectOptions(select, options);
+    }""")
+    return node
 end
 
 struct LoadAnimation
     visible::Observable{Bool}
+    dom_id
 end
 
-LoadAnimation() = LoadAnimation(Observable(true))
+LoadAnimation() = LoadAnimation(Observable(true), dom_id())
 
 function loadanimation(controller)
     la = LoadAnimation()
@@ -1203,12 +1009,13 @@ function loadanimation(controller)
     return la
 end
 
-function JSServe.jsrender(session::Session, la::LoadAnimation)
-    dom = DOM.div(DOM.div();
-                  id="load-animation",
-                  class="la-ball-clip-rotate")
-    onjs(session, la.visible, js"(b) => $(dom).style.display = (b ? 'block' : 'none')")
-    r(session, dom)
+function lowerdom(::LoadAnimation)
+    DOM.div(DOM.div(); id="load-animation", class="la-ball-clip-rotate")
+end
+function Bonito.jsrender(session::Session, la::LoadAnimation)
+    node = lowerdom(session, la)
+    onjs(session, la.visible, js"(b)=>{ $node.style.display = (b ? 'block' : 'none'); }")
+    return node
 end
 
 struct TwoOptionDialog
@@ -1219,6 +1026,7 @@ struct TwoOptionDialog
     button2::Observable{String}
     visible::Observable{Bool}
     attributes
+    dom_id
 end
 
 TwoOptionDialog(; kwargs...) = TwoOptionDialog(Observable(true),
@@ -1227,7 +1035,8 @@ TwoOptionDialog(; kwargs...) = TwoOptionDialog(Observable(true),
                                                Observable(""),
                                                Observable(""),
                                                Observable(false),
-                                               with_attributes(kwargs; style="display:none"))
+                                               with_attributes(kwargs; style="display:none"),
+                                               dom_id())
 
 twooptiondialog(controller) = get!(gethub(controller), :twooptiondialog, TwoOptionDialog())
 
@@ -1251,28 +1060,27 @@ function twooptiondialog(controller, answer, title, message, button1, button2)
     end
 end
 
-function JSServe.jsrender(session::Session, tod::TwoOptionDialog)
-    dom = DOM.div(DOM.div(tod.title[]; id="two-option-dialog-title"),
-                  DOM.div(tod.prompt[]; id="two-option-dialog-prompt"),
-                  DOM.div(
-                  DOM.button(tod.button1[];
-                             type="button",
-                             id="two-option-dialog-button1",
-                             class="button",
-                             onclick=js"""
-                             JSServe.update_obs($(tod.answer), true);
-                             """),
-                  DOM.button(tod.button2[];
-                             type="button",
-                             id="two-option-dialog-button2",
-                             class="button",
-                             onclick=js"""
-                             JSServe.update_obs($(tod.answer), false);
-                             """);
-                  id="two-option-dialog-buttons");
-                  id="two-option-dialog",
-                  tod.attributes...)
-    onjs(session, tod.visible, js"(b) => $(dom).style.display = (b ? 'block' : 'none')")
-    r(session, dom)
+function lowerdom(tod::TwoOptionDialog)
+    DOM.div(DOM.div(tod.title[]; id="two-option-dialog-title"),
+            DOM.div(tod.prompt[]; id="two-option-dialog-prompt"),
+            DOM.div(
+                    DOM.button(tod.button1[];
+                               type="button",
+                               id="two-option-dialog-button1",
+                               class="button",
+                               onclick=js"()=>{ $(tod.answer).notify(true); }"),
+                    DOM.button(tod.button2[];
+                               type="button",
+                               id="two-option-dialog-button2",
+                               class="button",
+                               onclick=js"()=>{ $(tod.answer).notify(false); }");
+                    id="two-option-dialog-buttons");
+            id="two-option-dialog",
+            tod.attributes...)
+end
+function Bonito.jsrender(session::Session, tod::TwoOptionDialog)
+    node = lowerdom(session, tod)
+    onjs(session, tod.visible, js"(b)=>{ $node.style.display = (b ? 'block' : 'none'); }")
+    return node
 end
 
-- 
GitLab


From 1142f4e010f5dd22a7b7a56d9eff7887e431c8b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Sun, 17 Nov 2024 16:32:59 +0100
Subject: [PATCH 10/18] feat: support for venv-installed backends

---
 src/Taggers.jl | 81 ++++++++++++++++++++++++++------------------------
 1 file changed, 42 insertions(+), 39 deletions(-)

diff --git a/src/Taggers.jl b/src/Taggers.jl
index 77987c9..23dfa19 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -17,17 +17,37 @@ end
 Tagger(backend_dir, model_instance) = Tagger(string(backend_dir), string(model_instance))
 
 function isolate(tagger)
-    mkpath(joinpath(tagger.backend_dir, "data", "raw"))
-    rawdatadir = mktempdir(joinpath(tagger.backend_dir, "data", "raw"); cleanup=false)
+    rawdatadir = joinpath(tagger.backend_dir, "data", "raw")
+    mkdir(rawdatadir)
+    rawdatadir = mktempdir(rawdatadir; cleanup=false)
     Tagger(tagger.backend_dir, tagger.model_instance, basename(rawdatadir),
            tagger.output_filenames)
 end
 
-isbackend(path) = isdir(joinpath(path, "models")) &&
-                  isfile(joinpath(path, "pyproject.toml")) &&
-                  isfile(joinpath(path, "poetry.lock"))
+const envdir = get(ENV, "VENV_DIR", "venv")
+
+function isbackend(path)
+    bindir = Sys.iswindows() ? "Scripts" : "bin"
+    return isdir(joinpath(path, "models")) &&
+           isfile(joinpath(path, "pyproject.toml")) &&
+           (isfile(joinpath(path, "poetry.lock")) ||
+            (isfile(joinpath(path, envdir, bindir, "python")) &&
+             isfile(joinpath(path, envdir, bindir, "tagging-backend"))))
+end
 isbackend(tagger::Tagger) = isbackend(tagger.backend_dir)
 
+function tagging_backend_command(path)
+    bindir = Sys.iswindows() ? "Scripts" : "bin"
+    python = joinpath(path, envdir, bindir, "python")
+    tagging_backend_script = joinpath(path, envdir, bindir, "tagging-backend")
+    if isfile(python) && isfile(tagging_backend_script)
+        `$python $tagging_backend_script`
+    else
+        `poetry run tagging-backend`
+    end
+end
+tagging_backend_command(tagger::Tagger) = tagging_backend_command(tagger.backend_dir)
+
 modeldir(tagger::Tagger) = joinpath(tagger.backend_dir, "models", tagger.model_instance)
 
 datadir(tagger::Tagger, stage::String) = joinpath(tagger.backend_dir, "data", stage,
@@ -205,56 +225,39 @@ function parsekwargs!(args, kwargs)
     return args
 end
 
-function train(tagger::Tagger; pretrained_instance=nothing, kwargs...)
+function run(tagger, switch, kwargs, extra...)
     args = ["--model-instance", tagger.model_instance]
-    if !isnothing(pretrained_instance)
-        push!(args, "--pretrained-model-instance")
-        push!(args, pretrained_instance)
+    for (p, v) in pairs(extra)
+        if !isnothing(v)
+            push!(args, p)
+            push!(args, v)
+        end
     end
     if !isnothing(tagger.sandbox)
         push!(args, "--sandbox")
         push!(args, tagger.sandbox)
     end
     parsekwargs!(args, kwargs)
-    ret = run(Cmd(`poetry run tagging-backend train $(args)`; dir=tagger.backend_dir))
+    cmd = tagging_backend_command(tagger)
+    Base.run(Cmd(`$(cmd) $(switch) $(args)`; dir=tagger.backend_dir))
+end
+
+function train(tagger::Tagger; pretrained_instance=nothing, kwargs...)
+    ret = run(tagger, "train", kwargs,
+              "--pretrained-model-instance" => pretrained_instance)
     @assert isdir(modeldir(tagger))
     return ret
 end
 
-function predict(tagger::Tagger; kwargs...)
-    args = ["--model-instance", tagger.model_instance]
-    if !isnothing(tagger.sandbox)
-        push!(args, "--sandbox")
-        push!(args, tagger.sandbox)
-    end
-    parsekwargs!(args, kwargs)
-    run(Cmd(`poetry run tagging-backend predict $(args)`; dir=tagger.backend_dir))
-end
+predict(tagger::Tagger; kwargs...) = run(tagger, "predict", kwargs)
 
 function finetune(tagger::Tagger; original_instance=nothing, kwargs...)
-    args = ["--model-instance", tagger.model_instance]
-    if !isnothing(original_instance)
-        push!(args, "--original-model-instance")
-        push!(args, original_instance)
-    end
-    if !isnothing(tagger.sandbox)
-        push!(args, "--sandbox")
-        push!(args, tagger.sandbox)
-    end
-    parsekwargs!(args, kwargs)
-    ret = run(Cmd(`poetry run tagging-backend finetune $(args)`; dir=tagger.backend_dir))
+    ret = run(tagger, "finetune", kwargs,
+              "--original-model-instance" => original_instance)
     @assert isdir(modeldir(tagger))
     return ret
 end
 
-function embed(tagger::Tagger; kwargs...)
-    args = ["--model-instance", tagger.model_instance]
-    if !isnothing(tagger.sandbox)
-        push!(args, "--sandbox")
-        push!(args, tagger.sandbox)
-    end
-    parsekwargs!(args, kwargs)
-    run(Cmd(`poetry run tagging-backend embed $(args)`; dir=tagger.backend_dir))
-end
+embed(tagger::Tagger; kwargs...) = run(tagger, "embed", kwargs)
 
 end # module
-- 
GitLab


From 72c214c92cf9044fbe02c49606773bb9e340d5cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Sun, 17 Nov 2024 16:43:51 +0100
Subject: [PATCH 11/18] fix: backend menu bug following MR !21

---
 src/wgl.jl | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/wgl.jl b/src/wgl.jl
index c21d40f..9bdb2a7 100644
--- a/src/wgl.jl
+++ b/src/wgl.jl
@@ -905,7 +905,7 @@ end
 function filemenu(controller; kwargs...)
     wd = getworkingdir(controller)
     dir = joinpath(wd.root, wd.path[])
-    browser = FileBrowser(dir; root=wd.root, upload_button=true)
+    browser = FileBrowser(dir; root=wd.root, upload_button=false)
     on(FileBrowsers.selectedfile(browser)) do file
         tryopenfile(controller, file; reload=true)
     end
@@ -916,9 +916,11 @@ function filemenu(controller; kwargs...)
         wd.path[] = dir
         wd.content[] = [d for (_, d) in browser.model.content[]]
     end
-    on(FilePickers.uploadedfile(browser)) do fileinfo
-        filepath = joinpath(workingdir[], fileinfo["name"])
-        saveinputfile(filepath, fileinfo["content"])
+    if FileBrowsers.supports_upload(browser)
+        on(FilePickers.uploadedfile(browser)) do fileinfo
+            filepath = joinpath(workingdir[], fileinfo["name"])
+            saveinputfile(filepath, fileinfo["content"])
+        end
     end
     return FileMenu(gethub(controller), browser)
 end
@@ -990,7 +992,7 @@ end
 function Bonito.jsrender(session::Session, bs::BackendMenu)
     node = lowerdom(session, bs)
     onjs(session, bs.backends.model_instances, js"""(options) => {
-         select = document.querySelector($(dom_selector(bs)) + ' > select');
+         select = document.querySelectorAll($(dom_selector(bs)) + ' > select')[1];
          LarvaTagger.updateSelectOptions(select, options);
     }""")
     return node
-- 
GitLab


From cfef5d4f9569cfc86909e1fdfcd1e4ee23b8c5a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Tue, 19 Nov 2024 17:11:52 +0100
Subject: [PATCH 12/18] fix: macOS-specific fixes, --view-factor is reverted
 back to 1 by default

---
 README.md          | 68 ++++++++++++++++++++++++++++------------
 scripts/install.sh | 78 +++++++++++++++++++++++++++++++++-------------
 src/Taggers.jl     |  3 +-
 src/cli_open.jl    |  6 ++--
 4 files changed, 109 insertions(+), 46 deletions(-)

diff --git a/README.md b/README.md
index eaf29e8..1b9a1b2 100644
--- a/README.md
+++ b/README.md
@@ -18,9 +18,9 @@ This package features a web-based graphical user interface (GUI) for visualizing
 
 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 in combination with [*MaggotUBA*](https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter) for the identification of larval actions or postures.
+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.
 
-> The term *LarvaTagger* refers to the full software with all the currently available tagging capabilities included.
+> The term *LarvaTagger* refers to the full software with MaggotUBA-based tagging capabilities included.
 
 *Docker* images are available for *LarvaTagger*. See the [dedicated instructions page](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/tree/main/recipes) or the [Quick start](#quick-start-with-docker) section below.
 
@@ -37,6 +37,8 @@ The present section will guide you through the following steps:
 
 These instructions only require [*Docker*](https://docs.docker.com/get-docker/) to be available, more specifically the *Docker Engine*. On Windows and macOS, *Docker Engine* is provided by the *Docker Desktop* product. *Docker Desktop* is free of charge.
 
+> On macOS, make sure to enable Rosetta 2 for Apple Silicon architecture as specified in the [“System requirements” section](https://docs.docker.com/desktop/setup/install/mac-install/). Docker VMM may be a better option, especially for M2+ computers, but has not been tested so far.
+
 Ensure the currently logged-in user has access to *Docker*, and the *Docker* engine is running.
 
 The *Docker* image is obtained and operated using a script:
@@ -56,16 +58,16 @@ chmod a+x larvatagger.sh
 
 The demo data can be opened in the web browser for visual inspection, on macOS and Linux with:
 ```
-./larvatagger.sh open Masson_et_al_2020.label --browser
+./larvatagger.sh open Masson_et_al_2020.label
 ```
 and on Windows with:
 ```
-larvatagger.bat open Masson_et_al_2020.label --browser
+larvatagger.bat open Masson_et_al_2020.label
 ```
 
-The viewer should open in a new tab in the default web browser. If no tabs appear, open a new one and go to [http://127.0.0.1:9284](http://127.0.0.1:9284).
+Once told to do so, go to [http://127.0.0.1:9284](http://127.0.0.1:9284) in a web browser.
 
-Not all the web browsers are supported. Prefer Firefox or Chrome-like browsers.
+Not all the web browsers are supported. Prefer Firefox or Chrome-like browsers. In particular, Safari is not supported.
 
 Note that, on the first execution, the *Docker* image will be downloaded. This is done once, and can be considered as the installation step.
 
@@ -73,7 +75,9 @@ Note that, on the first execution, the *Docker* image will be downloaded. This i
 
 ### Main controls
 
-The tracked larvae can be animated pressing the "Play/Pause" button of the player below the 2D view.
+> If the 2D viewer looks small, restart LarvaTagger with additional option `--view-factor 2`.
+
+The tracked larvae can be animated pressing the play button of the player below the 2D view.
 
 From the view displaying multiple larvae, a larva can be selected. As a result, the 2D view displays the selected larva only. From this single-larva view, the top right button allows going back to the multi-larva view.
 
@@ -92,6 +96,8 @@ Alternatively, tags can be similarly *un*assigned at individual time steps or ov
 
 [Using the *Docker* image](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/tree/main/recipes) may come handy, as it ships *LarvaTagger* with the default *MaggotUBA*-based tagger as a standalone package and is easier to update.
 
+Alternatively, an install script is provided to install LarvaTagger with MaggotUBA-based tagging capabilities.
+
 ### Using the *scripts/install.sh* script
 
 On macOS, Linux and WSL, a [script](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/blob/dev/scripts/install.sh?ref_type=heads) is provided to automate the installation process from source. This script installs LarvaTagger.jl and Julia if missing. It also adds the *larvatagger* command to the user's path variable.
@@ -99,7 +105,7 @@ The *scripts/install.sh* script can be run with:
 ```
 /bin/bash -c "$(curl -sSL "https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/raw/dev/scripts/install.sh?ref_type=heads&inline=false")"
 ```
-To install the full LarvaTagger suite (Linux and WSL only!), run instead:
+To install the full LarvaTagger suite on Linux or WSL, run instead:
 ```
 curl -sSL "https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/raw/dev/scripts/install.sh?ref_type=heads&inline=false" | /bin/bash -s -- --with-default-backend
 ```
@@ -110,11 +116,16 @@ 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.
 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
+```
+
 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.
 
 ### Manually from source
 
-To natively run *LarvaTagger.jl* instead, you will need [*julia>=1.7*](https://julialang.org/downloads/).
+To manually install *LarvaTagger.jl* (without automatic tagging capabilities), you will need [*julia>=1.7*](https://julialang.org/downloads/).
 
 If you are not familiar with installing *Julia*, you may appreciate installation helpers such as [*Juliaup*](https://github.com/JuliaLang/juliaup) or [*jill*](https://pypi.org/project/jill/).
 
@@ -167,7 +178,7 @@ If the alternative installation procedure was followed instead, go to the *Larva
 julia --project=. -e 'using Pkg; Pkg.update()'
 ```
 
-## Launching the graphical user interface
+## Launching the graphical/web user interface
 
 The GUI is provided by a web server and can be accessed using a web browser, preferably Firefox or Chrome-like browsers.
 
@@ -175,6 +186,12 @@ Running *LarvaTagger.jl* sets the server up. Once the server is ready, the app c
 
 See the [Quick start](#main-controls) section for information about some of the controls available in the GUI.
 
+The launching procedure depends on the followed installation procedure.
+* With manual installations using Julia, the web UI can be served from the `julia` REPL (advanced).
+* The *larvatagger* script can be used instead to launch the web UI from the command-line.
+* Docker users should use the *larvatagger.sh* or *larvatagger.ps1* scripts instead; beware that their usage differs from that of the *larvatagger* script.
+* Users of the *install.sh* script benefit from a slightly simplified *larvatagger* command, added to the PATH environment variable.
+
 ### Using the *larvatagger* script
 
 If you cloned the repository, we recommend you run *LarvaTagger* using the *larvatagger* script to be found in the *scripts* directory:
@@ -183,22 +200,22 @@ If you cloned the repository, we recommend you run *LarvaTagger* using the *larv
 scripts/larvatagger open path/to/data/file --browser
 ```
 
-If LarvaTagger was installed using the *scripts/install.sh* script, the *larvatagger* script should be in the user's path environment variable and, as a consequence, available from everywhere in the commandline:
+If LarvaTagger was installed using the *scripts/install.sh* script, the *larvatagger* script should be in the user's path environment variable and, as a consequence, available from everywhere in the command-line:
 ```
-larvatagger open path/to/data/file --browser
+larvatagger open path/to/data/file
 ```
 
 The script will actually open a *Julia* interpreter, and give some guidance on how to exit the interpreter.
 
-> For now, `larvatagger open` cannot be run with no input arguments. A track data file is required.
+> Since version 0.19, the data file argument is no longer required. Data files can be opened from the Web UI.
 
-The `--browser` argument may open a new tab in your web browser, but this feature is known to be ineffective in some situations. In such an event, open a new tab and go to [http://localhost:9284](http://127.0.0.1:9284).
+The `--browser` argument may open a new tab in your web browser, but this feature is known to be ineffective in some situations. In such an event, open a new tab and go to [http://localhost:9284](http://127.0.0.1:9284). The argument is passed by default by the *larvatagger* command, in contrast to the *scripts/larvatagger* script.
 
 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, in the *LarvaTagger* directory created above, launch the *Julia* interpreter:
+As an alternative to the *larvatagger* script or command, in the *LarvaTagger* directory created above, launch the *Julia* interpreter:
 ```
 julia --project=.
 ```
@@ -211,16 +228,15 @@ To exit the interpreter, type `exit()` or press Ctrl+D.
 
 ### macOS
 
-On macOS computers, the 2D larva view may show up twice as small as expected. To mitigate this undesired behavior,
-`larvatagger open` admits a `--view-factor` option, and equivalently `larvaeditor` admits a `viewfactor` argument.
-This option/argument is 2 per default on macOS, 1 on the other platforms.
+On some computers (typically macOS computers), the 2D larva view may show up twice as small as expected.
+To mitigate this undesired behavior, `larvatagger open` admits a `--view-factor` option, and equivalently `larvaeditor` admits a `viewfactor` argument.
 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 installed with the *scripts/install.sh* script and the `--with-default-backend` option.
+*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.
 
-To extend the editor with *MaggotUBA*-based automatic tagging, see the [recommended installation steps for *TaggingBackends* and *MaggotUBA*](https://gitlab.pasteur.fr/nyx/TaggingBackends#recommended-installation).
+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).
 
 > Strictly speaking, the action identification package (or tagging backend) is called *MaggotUBA-adapter*.
 > It is based on *MaggotUBA*, which is an autoencoder for action-agnostic behavior analysis.
@@ -239,6 +255,7 @@ Similarly, to let *LarvaTagger* know about *MaggotUBA*:
 ```
 scripts/larvatagger open <path/to/data/file> --backends=<path/to/MaggotUBA/parent/directory> --browser
 ```
+The `--backends` argument is not necessary with the *larvatagger* **command** (`scripts/larvatagger` denotes the *larvatagger* **script** to be found in the *scripts* directory of the LarvaTagger.jl project).
 
 The *larvatagger* script can also be used to train a new tagger:
 ```
@@ -249,6 +266,11 @@ and apply this tagger to a tracking data file:
 scripts/larvatagger predict <path/to/backend> <tagger-name> <path/to/data/file>
 ```
 
+With the *larvatagger* command, the path-to-backend argument is still required (may change in the future), and the full command to run the default 20230311 tagger for example is:
+```
+larvatagger predict ~/.local/share/larvatagger/MaggotUBA 20230311 <path/to/data/file>
+```
+
 Among the many optional arguments to the `train` command, an important one is `--iterations`. It allows specifying the training budget. In several applications, higher training scores were achieved increasing the value for this argument. The default for *MaggotUBA-adapter* tagging backend is 1000, which may be too few iterations in many cases.
 
 *MaggotUBA-adapter* admits either a single value or a comma-separated pair of values. Indeed, *MaggotUBA-adapter* training is performed in two phases: first the classifier stage is trained, with static weights in the pretrained *MaggotUBA* encoder; second both the classifier and encoder are fine-tuned. A higher training budget for the second fine-tuning stage may significantly increase the training accuracy.
@@ -293,3 +315,9 @@ On calling `larvatagger predict` using a *MaggotUBA*-based tagger, if *CUDA* com
 .../torch/cuda/__init__.py:... Can't initialize NVML
 ```
 you might have upgraded your *NVIDIA* drivers and not rebooted the OS. Try restarting your computer.
+
+### Display size
+
+On some computers, the 2D viewer is abnormally small. This results from multiple sizing modalities (pixels for the 2D viewer, font sizes for the other parts of the web UI) and local defaults that cannot be probed. As a consequence, the user is invited to pass argument `--view-factor` followed by a value that scales the 2D viewer only (try values `1.5` and `2` for example).
+
+See also issue [#16](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/issues/16).
diff --git a/scripts/install.sh b/scripts/install.sh
index 994b588..a1a2b1d 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -24,7 +24,8 @@ if [ "$1" = "--uninstall" ]; then
   rm -rf "$BIN_DIR/larvatagger"
   rm -rf "$LARVATAGGER_PATH"
 
-  # testing only; does not apply to all platforms; do not pass the --full option!
+  # testing only; deletes Poetry, PyEnv and JuliaUp, and does not check beforehands
+  # whether these dependencies were installed by the present script or not
   if [ "$2" = "--full" ]; then
     PYTHON="python3"
     if [[ "`python3 -V`" =~ "Python 3.6" ]] && command -v python3.8 &>/dev/null; then
@@ -35,7 +36,7 @@ if [ "$1" = "--uninstall" ]; then
     command -v pyenv &>/dev/null && rm -rf $(pyenv root)
     command -v juliaup &>/dev/null && juliaup self uninstall
     rm -rf ~/.juliaup
-    # TODO: clean up .bash_profile for pyenv-related stuff and restart the shell
+    # TODO: clean up .bash_profile for pyenv-related stuff, .bashrc/.zshrc for PATH manipulations, and restart the shell
     rm -rf ~/.julia
   fi
 else
@@ -64,27 +65,27 @@ for arg in "$@"; do
     PYTHON_VERSION=3.11
   elif [ "$arg" = "--free-python-dependencies" ]; then
     internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=1
+  elif [ "$arg" = "--lock-python-dependencies" ]; then
+    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=0
   fi
 done
 
 PYTHON="python$PYTHON_VERSION"
 
-if ! command -v curl &>/dev/null; then
-  if command -v brew &>/dev/null; then
-    # macOS users are not given the choice as they usually do not care about freedom
-    brew install curl
-  fi
-fi
-if ! command -v curl &>/dev/null; then
-  echo "Command curl required; aborting"
-else
-
 check_brew() {
   if [ "`uname`" = "Darwin" ]; then
     # macOS
     if ! command -v brew &>/dev/null; then
-      echo "Installing Homebrew; admin rights will be requested"
-      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+      if ! [ -f /opt/homebrew/bin/brew ]; then
+        echo "Installing Homebrew; admin rights will be requested"
+        /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+      fi
+      # not sure why zsh does not support ~/ below, while it seems to behave properly elsewhere...
+      cat <<"EOF" >>$HOME/.zprofile
+
+eval "$(/opt/homebrew/bin/brew shellenv)"
+EOF
+      eval "$(/opt/homebrew/bin/brew shellenv)"
     fi
   fi
 }
@@ -97,10 +98,27 @@ if ! command -v realpath &>/dev/null; then
   fi
 fi
 
+if ! command -v curl &>/dev/null; then
+  if [ "`uname`" = "Darwin" ]; then
+    check_brew
+    # macOS users are not given the choice as they usually do not care about freedom
+    brew install curl
+  fi
+fi
+if ! command -v curl &>/dev/null; then
+  echo "Command curl required; aborting"
+else
+
 if [ -z "$JULIA_VERSION" ]; then
   JULIA_VERSION=1.10
+  JULIA_CHANNEL=lts
 else
   echo "Using environment variable: JULIA_VERSION= $JULIA_VERSION"
+  if [ -z "$JULIA_CHANNEL" ]; then
+    JULIA_CHANNEL=$JULIA_VERSION
+  else
+    echo "Using environment variable: JULIA_CHANNEL= $JULIA_CHANNEL"
+  fi
 fi
 
 JULIA="julia"
@@ -112,16 +130,18 @@ install_juliaup() {
     echo "Using environment variable: JULIA_INSTALL_ARGS= $JULIA_INSTALL_ARGS"
   fi
   curl -fsSL https://install.julialang.org | sh -s -- $JULIA_INSTALL_ARGS --default-channel $JULIA_VERSION
-  if [ -f ~/.bashrc -a -n "`grep "# >>> juliaup initialize >>>" ~/.bashrc`" ]; then
-    source ~/.bashrc
-  elif [ -f ~/.bash_profile -a -n "`grep "# >>> juliaup initialize >>>" ~/.bash_profile`" ]; then
-    source ~/.bash_profile
-  fi
+  export PATH=~/.juliaup/bin:$PATH
 }
 if ! command -v $JULIA &>/dev/null; then
   install_juliaup
 elif ! [[ "`$JULIA -v`" =~ "julia version $JULIA_VERSION" ]]; then
-  install_juliaup
+  if ! command -v juliaup &>/dev/null; then
+    install_juliaup
+  fi
+fi
+if command -v juliaup &>/dev/null; then
+  juliaup add $JULIA_CHANNEL
+  juliaup default $JULIA_CHANNEL
 fi
 
 add_local_bin_to_path() {
@@ -144,7 +164,7 @@ EOF
   else
     echo "the larvatagger command is available in directory:"
     echo "  $BIN_DIR"
-    echo "consider adding the directory to the PATH variable"
+    echo "consider adding the directory to the PATH variable in your rc or profile file"
   fi
   export PATH=$PATH:$BIN_DIR
 }
@@ -206,6 +226,11 @@ if [ -n "$WITH_BACKEND" ]; then
       check_brew
       if command -v brew &>/dev/null; then
         brew install python@$PYTHON_VERSION
+        if ! command -v python$PYTHON_VERSION &>/dev/null; then
+          echo "Try instead $0 --with-backend --experimental"
+          echo "Aborting..."
+          exit 1
+        fi
       else
         echo "WARNING: command $PYTHON not found"
       fi
@@ -290,7 +315,7 @@ else
   curl -fsSL https://gitlab.pasteur.fr/nyx/TaggingBackends/-/archive/${TAGGINGBACKENDS_BRANCH}/TaggingBackends-${TAGGINGBACKENDS_BRANCH}.tar.gz | tar zxv
   mv TaggingBackends-${TAGGINGBACKENDS_BRANCH} TaggingBackends
 fi
-(cd TaggingBackends && activate && PYTHON=`command -v python` && $JULIA --project=. -e 'using Pkg; Pkg.instantiate()')
+(cd TaggingBackends && activate && PYTHON="`poetry env info`/bin/python" $JULIA --project=. -e 'using Pkg; Pkg.instantiate()')
 [ -d PlanarLarvae ] && (cd TaggingBackends && $JULIA --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
 (cd TaggingBackends && activate && JULIA_PROJECT=$(pwd) poetry install)
 
@@ -322,6 +347,15 @@ fi
 export JULIA_PROJECT=$(realpath TaggingBackends)
 
 if [ -z "$internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES" ]; then
+  if [ "`uname`" = "Darwin" ]; then
+    # PyTorch requirements in the requirements.txt file are Cuda-based, suitable for Windows and Linux
+    # while alternative libraries are used on macOS
+    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=1
+  else
+    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=0
+  fi
+fi
+if [ "$internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES" = "0" ]; then
   (cd MaggotUBA && activate && (cat requirements.txt | xargs -I % sh -c 'poetry add "%"' || true) && poetry install -v)
 else
   (cd MaggotUBA && activate && poetry install -v)
diff --git a/src/Taggers.jl b/src/Taggers.jl
index 23dfa19..637a90c 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -169,7 +169,8 @@ end
 function pull(tagger::Tagger, dest_dir::String)
     proc_data_dir = datadir(tagger, "processed")
     isdir(proc_data_dir) || throw("no processed data directory found")
-    dest_dir = realpath(dest_dir) # strip end slash
+    # dest_dir can be empty on macOS
+    dest_dir = isempty(dest_dir) ? pwd() : realpath(dest_dir) # strip end slash
     dest_files = String[]
     for (parent, _, files) in walkdir(proc_data_dir)
         if !isempty(files)
diff --git a/src/cli_open.jl b/src/cli_open.jl
index 6f10ed6..56ab8b5 100644
--- a/src/cli_open.jl
+++ b/src/cli_open.jl
@@ -65,8 +65,8 @@ function main(args=ARGS; exit_on_error=false)
     viewfactor = parsed_args["--view-factor"]
     if !isnothing(viewfactor)
         kwargs[:viewfactor] = parse(Float64, viewfactor)
-    elseif Sys.isapple()
-        kwargs[:viewfactor] = 2
+    # elseif Sys.isapple()
+    #     kwargs[:viewfactor] = 2
     end
 
     if parsed_args["--viewer"]
@@ -84,7 +84,7 @@ function main(args=ARGS; exit_on_error=false)
     port = isnothing(port) ? 9284 : parse(Int, port)
     server = Server(app, "0.0.0.0", port)
     if parsed_args["--browser"]
-        Bonito.openurl("http://127.0.0.1:$(port)")
+        Bonito.HTTPServer.openurl("http://127.0.0.1:$(port)")
     end
     if verbose
         @info "The server is ready at http://127.0.0.1:$(port)"
-- 
GitLab


From bb80ddd55bf0945771849055f6a331829d68e5ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Thu, 21 Nov 2024 16:30:11 +0100
Subject: [PATCH 13/18] fix: type error in cli argument handling

---
 src/Taggers.jl          |  2 +-
 test/deploy_and_test.sh | 75 +++++++++++------------------------------
 2 files changed, 20 insertions(+), 57 deletions(-)

diff --git a/src/Taggers.jl b/src/Taggers.jl
index 637a90c..206406f 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -227,7 +227,7 @@ function parsekwargs!(args, kwargs)
 end
 
 function run(tagger, switch, kwargs, extra...)
-    args = ["--model-instance", tagger.model_instance]
+    args = Any["--model-instance", tagger.model_instance]
     for (p, v) in pairs(extra)
         if !isnothing(v)
             push!(args, p)
diff --git a/test/deploy_and_test.sh b/test/deploy_and_test.sh
index 42a2db6..91bc7e8 100755
--- a/test/deploy_and_test.sh
+++ b/test/deploy_and_test.sh
@@ -10,22 +10,21 @@
 # * LOCAL_SCENARII: can be set to 1 to overwrite LarvaTagger.jl's test/scenarii.sh file
 #   with the scenarii.sh file that comes along the present script (same directory)
 # * LAUNCHER: default is "srun" if Slurm is available, otherwise nothing
-# * JULIA_PROJECT: where to find PyCall (do not modify)
 
 # Example on Maestro (GPU-accelerated):
 #   SLURM_OPTS='-p dbc_pmo -q dbc -c 4' JULIA_THREADS=4 ./deploy_and_test.sh
 # or (CPU-only):
 #   SLURM_OPTS='-p dedicated -q fast -c 16' JULIA_THREADS=16 ./deploy_and_test.sh
 
-if [ -n "$1" ] && [ "$1" = "-f" ] || [ "$1" = "--force" ]; then
-  rm -rf LarvaTagger
-elif [ -d LarvaTagger ]; then
-  echo "\`$0\` needs to make a local LarvaTagger directory"
-  echo "but this directory already exists."
-  echo "Run \`$0 --force\` or pick another location."
+if ! [ -f scripts/install.sh ]; then
+  echo "Call `basename $0` from the project's root directory:"
+  echo "    test/deploy_and_test.sh"
   exit 1
 fi
 
+scripts/install.sh --uninstall
+scripts/install.sh --with-backend --experimental
+
 #############
 ## Maestro ##
 #############
@@ -33,13 +32,6 @@ fi
 # SSL verification can be disabled for git clone to work:
 #   export GIT_SSL_NO_VERIFY=true
 
-if ! julia -v &> /dev/null; then
-  # do NOT use juliaup to install Julia
-  module load Python &> /dev/null # Maestro
-  python3 -m pip install jill
-  python3 -m jill install -v 1.10.0 -c
-fi
-
 # h5diff (for tests) on Maestro
 if ! h5diff -V &> /dev/null; then
   module load hdf5 &> /dev/null
@@ -60,68 +52,39 @@ fi
 #############
 
 CURDIR=$(pwd)
-
-mkdir -p LarvaTagger && cd $_
+LTROOT=$HOME/.local/share/larvatagger/LarvaTagger.jl
 
 # shunit2 is for tests specifically
 if [ -z "$(which shunit2)" ]; then
-  git clone -c advice.detachedHead=false -b v2.1.8 https://github.com/kward/shunit2
+  (cd "$LTROOT/test" && \
+    git clone -c advice.detachedHead=false -b v2.1.8 https://github.com/kward/shunit2)
 fi
 
-## comment out the below line to build a development environment
-#CLONE_OPTS=" --depth 1 --no-tags --single-branch -b dev"
-#git clone$CLONE_OPTS https://gitlab.pasteur.fr/nyx/planarlarvae.jl PlanarLarvae
-#git clone$CLONE_OPTS https://gitlab.pasteur.fr/nyx/LarvaTagger.jl
-BRANCH=dev
-wget -O- https://gitlab.pasteur.fr/nyx/planarlarvae.jl/-/archive/${BRANCH}/planarlarvae.jl-${BRANCH}.tar.gz | tar zxv && mv planarlarvae.jl-${BRANCH} PlanarLarvae
-wget -O- https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/archive/${BRANCH}/larvatagger.jl-${BRANCH}.tar.gz | tar zxv && mv larvatagger.jl-${BRANCH} LarvaTagger.jl
-ln -s $(realpath LarvaTagger.jl) LarvaTagger
-#git clone$CLONE_OPTS https://gitlab.pasteur.fr/nyx/TaggingBackends
-#git clone$CLONE_OPTS https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter
-wget -O- https://gitlab.pasteur.fr/nyx/TaggingBackends/-/archive/${BRANCH}/TaggingBackends-${BRANCH}.tar.gz | tar zxv && mv TaggingBackends-${BRANCH} TaggingBackends
-wget -O- https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter/-/archive/${BRANCH}/MaggotUBA-adapter-${BRANCH}.tar.gz | tar zxv && mv MaggotUBA-adapter-${BRANCH} MaggotUBA-adapter
-ln -s $(realpath MaggotUBA-adapter) MaggotUBA
-
-# the poetry install step may not be necessary
-(cd TaggingBackends && julia --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.develop(path="../PlanarLarvae")' && poetry env use python3.8 && poetry install)
-# the step below may not be necessary
-(cd TaggingBackends && JULIA_PROJECT=$(pwd) poetry run python -c 'import julia; julia.install()')
-
-(cd MaggotUBA && poetry env use python3.8 && (cat requirements.txt | xargs -I % sh -c 'poetry add "%"') && poetry install -v && poetry remove taggingbackends && poetry add ../TaggingBackends && scripts/make_models.jl default)
-
-cd LarvaTagger || exit 1
-julia --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.develop(path="../PlanarLarvae")'
-
-# the remainder of this script is test-specific
-
-if [ -f ../../LarvaTagger_test_data.tgz ]; then
+if [ -f LarvaTagger_test_data.tgz ]; then
   # To generate the test data, first run the tests with environment variable KEEP_MODEL_FILES=1
   # copy the LarvaTagger/MaggotUBA/models/test_train_* directories into LarvaTagger/LarvaTagger.jl/test/data
   # and build the archive from the LarvaTagger/LarvaTagger.jl directory.
   # For example:
   #   KEEP_MODEL_FILES=1 ./deploy_and_test.sh
-  #   cd LarvaTagger/LarvaTagger.jl
+  #   cd ~/.local/share/larvatagger/LarvaTagger/LarvaTagger.jl
   #   rm -rf test/data/test_train_*
   #   cp -Rp ../MaggotUBA/models/test_train_* test/data/
   #   tar zcvf LarvaTagger_test_data.tgz test/data/*
-  #   mv LarvaTagger_test_data.tgz ../../
-  tar zxvf ../../LarvaTagger_test_data.tgz
+  (cd "$LTROOT" && tar zxvf LarvaTagger_test_data.tgz)
 else
   # Not recommended; reproducibility is not guarantee across hosts or architectures yet
   #wget -O- https://dl.pasteur.fr/fop/8MvgygD4/LarvaTagger_test_data.tgz | tar zxv
-  wget -O- https://dl.pasteur.fr/fop/WdnyHdKR/240208_LarvaTagger_test_data.tgz | tar zxv
+  (cd "$LTROOT" && \
+    wget -O- https://dl.pasteur.fr/fop/t8CtkCpY/240208_LarvaTagger_test_data.tgz | tar zxv)
 fi
 
 if [ "$LOCAL_SCENARII" = "1" ]; then
-  if [ -f "$CURDIR/scenarii.sh" ]; then cp "$CURDIR/scenarii.sh" test/
-  elif [ -f "$CURDIR/test/scenarii.sh" ]; then cp "$CURDIR/test/scenarii.sh" test/
-  else echo "Cannot find a local scenarii.sh file"
+  if [ -f "test/scenarii.sh" ]; then
+    cp "test/scenarii.sh" "$LTROOT/test/"
+  else
+    echo "Cannot find a local scenarii.sh file"
   fi
 fi
 
-if [ -z "$(which shunit2)" ]; then
-  ln -s "$CURDIR/LarvaTagger/shunit2/shunit2" test/
-fi
-
 echo "${launcher}test/scenarii.sh"
-${launcher}test/scenarii.sh
+(cd "$LTROOT" && ${launcher}test/scenarii.sh)
-- 
GitLab


From 28bfc90c7f02ce6f6f961d6d5bfce642e604082c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Thu, 21 Nov 2024 17:03:38 +0100
Subject: [PATCH 14/18] fix: argument parsing broken for train

---
 src/Taggers.jl | 27 +++++++++------------------
 1 file changed, 9 insertions(+), 18 deletions(-)

diff --git a/src/Taggers.jl b/src/Taggers.jl
index 206406f..ddae1b3 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -226,26 +226,17 @@ function parsekwargs!(args, kwargs)
     return args
 end
 
-function run(tagger, switch, kwargs, extra...)
-    args = Any["--model-instance", tagger.model_instance]
-    for (p, v) in pairs(extra)
-        if !isnothing(v)
-            push!(args, p)
-            push!(args, v)
-        end
-    end
-    if !isnothing(tagger.sandbox)
-        push!(args, "--sandbox")
-        push!(args, tagger.sandbox)
-    end
+function run(tagger, switch, kwargs)
+    kwargs["model_instance"] = tagger.model_instance
+    kwargs["sandbox"] = tagger.sandbox
+    args = Any[]
     parsekwargs!(args, kwargs)
     cmd = tagging_backend_command(tagger)
-    Base.run(Cmd(`$(cmd) $(switch) $(args)`; dir=tagger.backend_dir))
+    Base.run(Cmd(`$cmd $switch $args`; dir=tagger.backend_dir))
 end
 
-function train(tagger::Tagger; pretrained_instance=nothing, kwargs...)
-    ret = run(tagger, "train", kwargs,
-              "--pretrained-model-instance" => pretrained_instance)
+function train(tagger::Tagger; kwargs...)
+    ret = run(tagger, "train", kwargs)
     @assert isdir(modeldir(tagger))
     return ret
 end
@@ -253,8 +244,8 @@ end
 predict(tagger::Tagger; kwargs...) = run(tagger, "predict", kwargs)
 
 function finetune(tagger::Tagger; original_instance=nothing, kwargs...)
-    ret = run(tagger, "finetune", kwargs,
-              "--original-model-instance" => original_instance)
+    kwargs["original_model_instance"] = original_instance
+    ret = run(tagger, "finetune", kwargs)
     @assert isdir(modeldir(tagger))
     return ret
 end
-- 
GitLab


From 29b8ffede308fdd208b179eaf20555c53ee3d9a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Thu, 21 Nov 2024 18:21:56 +0100
Subject: [PATCH 15/18] fix: argument parsing broken for train (2)

---
 src/Taggers.jl | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/Taggers.jl b/src/Taggers.jl
index ddae1b3..09520c1 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -227,6 +227,7 @@ function parsekwargs!(args, kwargs)
 end
 
 function run(tagger, switch, kwargs)
+    kwargs = Dict{AbstractString, Any}(kwargs)
     kwargs["model_instance"] = tagger.model_instance
     kwargs["sandbox"] = tagger.sandbox
     args = Any[]
@@ -244,6 +245,7 @@ end
 predict(tagger::Tagger; kwargs...) = run(tagger, "predict", kwargs)
 
 function finetune(tagger::Tagger; original_instance=nothing, kwargs...)
+    kwargs = Dict{AbstractString, Any}(kwargs)
     kwargs["original_model_instance"] = original_instance
     ret = run(tagger, "finetune", kwargs)
     @assert isdir(modeldir(tagger))
-- 
GitLab


From 099bb7041c3d82eb87e1180bbdc13af32a2ee1cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Thu, 21 Nov 2024 18:26:53 +0100
Subject: [PATCH 16/18] fix: argument parsing broken for train (3)

---
 src/Taggers.jl | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/Taggers.jl b/src/Taggers.jl
index 09520c1..4844907 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -227,9 +227,9 @@ function parsekwargs!(args, kwargs)
 end
 
 function run(tagger, switch, kwargs)
-    kwargs = Dict{AbstractString, Any}(kwargs)
-    kwargs["model_instance"] = tagger.model_instance
-    kwargs["sandbox"] = tagger.sandbox
+    kwargs = Dict{Symbol, Any}(kwargs)
+    kwargs[:model_instance] = tagger.model_instance
+    kwargs[:sandbox] = tagger.sandbox
     args = Any[]
     parsekwargs!(args, kwargs)
     cmd = tagging_backend_command(tagger)
@@ -245,8 +245,8 @@ end
 predict(tagger::Tagger; kwargs...) = run(tagger, "predict", kwargs)
 
 function finetune(tagger::Tagger; original_instance=nothing, kwargs...)
-    kwargs = Dict{AbstractString, Any}(kwargs)
-    kwargs["original_model_instance"] = original_instance
+    kwargs = Dict{Symbol, Any}(kwargs)
+    kwargs[:original_model_instance] = original_instance
     ret = run(tagger, "finetune", kwargs)
     @assert isdir(modeldir(tagger))
     return ret
-- 
GitLab


From 704a6a5dc9f4aedcced9efb82f72cd453c3d4b43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Thu, 21 Nov 2024 23:28:34 +0100
Subject: [PATCH 17/18] fix: pretrained_instance properly mapped into
 --pretrained-model-instance

---
 src/Taggers.jl | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/Taggers.jl b/src/Taggers.jl
index 4844907..b66ef40 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -236,7 +236,9 @@ function run(tagger, switch, kwargs)
     Base.run(Cmd(`$cmd $switch $args`; dir=tagger.backend_dir))
 end
 
-function train(tagger::Tagger; kwargs...)
+function train(tagger::Tagger; pretrained_instance=None, kwargs...)
+    kwargs = Dict{Symbol, Any}(kwargs)
+    kwargs[:pretrained_model_instance] = pretrained_instance
     ret = run(tagger, "train", kwargs)
     @assert isdir(modeldir(tagger))
     return ret
-- 
GitLab


From 9e34ff1fefd0e440e15913eff8f3d0a8259f7801 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Wed, 27 Nov 2024 14:36:07 +0100
Subject: [PATCH 18/18] fix: test data updated

---
 recipes/Dockerfile      | 2 +-
 recipes/README.md       | 2 +-
 scripts/larvatagger.sh  | 4 +++-
 test/deploy_and_test.sh | 3 +--
 4 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/recipes/Dockerfile b/recipes/Dockerfile
index 6458645..4489a17 100644
--- a/recipes/Dockerfile
+++ b/recipes/Dockerfile
@@ -1,4 +1,4 @@
-FROM julia:1.10.4-bullseye AS base
+FROM julia:1.10.6-bullseye AS base
 
 ARG PROJECT_DIR=/app
 ARG BRANCH=main
diff --git a/recipes/README.md b/recipes/README.md
index 4259994..5ad9e8e 100644
--- a/recipes/README.md
+++ b/recipes/README.md
@@ -177,7 +177,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.18-standalone` image instead.
+If you are not interested in automatic tagging, use the `flaur/larvatagger:0.19-standalone` image instead.
 
 ### Upgrading
 
diff --git a/scripts/larvatagger.sh b/scripts/larvatagger.sh
index af552f9..3cf76b9 100755
--- a/scripts/larvatagger.sh
+++ b/scripts/larvatagger.sh
@@ -137,7 +137,9 @@ else
   echo "Using environment variable: TAGGINGBACKENDS_BRANCH= $TAGGINGBACKENDS_BRANCH"
 fi
 DOCKER_ARGS="--build-arg TAGGINGBACKENDS_BRANCH=$TAGGINGBACKENDS_BRANCH $DOCKER_ARGS"
-$docker build -t "$LARVATAGGER_IMAGE" ${DOCKER_ARGS}--build-arg BRANCH=$LARVATAGGER_BRANCH .
+DOCKER_BUILD="$docker build -t "$LARVATAGGER_IMAGE" ${DOCKER_ARGS}--build-arg BRANCH=$LARVATAGGER_BRANCH ."
+echo $DOCKER_BUILD
+eval $DOCKER_BUILD
 fi
 ;;
 
diff --git a/test/deploy_and_test.sh b/test/deploy_and_test.sh
index 91bc7e8..942ffd2 100755
--- a/test/deploy_and_test.sh
+++ b/test/deploy_and_test.sh
@@ -73,9 +73,8 @@ if [ -f LarvaTagger_test_data.tgz ]; then
   (cd "$LTROOT" && tar zxvf LarvaTagger_test_data.tgz)
 else
   # Not recommended; reproducibility is not guarantee across hosts or architectures yet
-  #wget -O- https://dl.pasteur.fr/fop/8MvgygD4/LarvaTagger_test_data.tgz | tar zxv
   (cd "$LTROOT" && \
-    wget -O- https://dl.pasteur.fr/fop/t8CtkCpY/240208_LarvaTagger_test_data.tgz | tar zxv)
+    wget -O- https://dl.pasteur.fr/fop/ppk8GBQf/241127_LarvaTagger_test_data.tgz | tar zxv)
 fi
 
 if [ "$LOCAL_SCENARII" = "1" ]; then
-- 
GitLab