diff --git a/Manifest.toml b/Manifest.toml
index 990bfdaa0182ec5e16aa3c3334342906352bf80b..e16f846d4be150f861b34a059f113ac47eec7797 100644
--- a/Manifest.toml
+++ b/Manifest.toml
@@ -954,11 +954,11 @@ version = "0.3.2"
 
 [[deps.PlanarLarvae]]
 deps = ["DelimitedFiles", "HDF5", "JSON3", "LinearAlgebra", "MAT", "Meshes", "OrderedCollections", "Random", "SHA", "StaticArrays", "Statistics", "StatsBase", "StructTypes"]
-git-tree-sha1 = "a6ced965b03efe596835f093d0ffbf1e8d991d50"
-repo-rev = "v0.14a2"
-repo-url = "https://gitlab.pasteur.fr/nyx/planarlarvae.jl"
+git-tree-sha1 = "6b2dc28d56bcef101672cbf2bb784bbd5d88d579"
+repo-rev = "main"
+repo-url = "https://gitlab.pasteur.fr/nyx/PlanarLarvae.jl"
 uuid = "c2615984-ef14-4d40-b148-916c85b43307"
-version = "0.14.0-a"
+version = "0.15.0"
 
 [[deps.PlotUtils]]
 deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "Statistics"]
diff --git a/Project.toml b/Project.toml
index 915301c48800ec0d4b2eb09ac64597c25d2215ca..a62f34aff4b9a40aba0ee45f68af67d672472fc2 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.16.4"
+version = "0.17"
 
 [deps]
 Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
diff --git a/README.md b/README.md
index 84d9df3189dbda6fc4b491ae840ed01b682a835c..73d662204d9a4e11b62e22ca3e060e5207f27ee0 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,13 @@ julia> using LarvaTagger; display(larvaeditor("path/to/data/file"))
 
 To exit the interpreter, type `exit()` or press Ctrl+D.
 
+### macOS
+
+On macOS computers, the 2D larva view often shows up twice as small as expected. To mitigate this undesired behavior,
+`larvatagger open` admits a `--view-factor` option, and `larvaeditor` admits a `viewfactor` argument.
+This option/argument is 2 per default on macOS, 1 on the other platforms.
+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.
@@ -125,6 +132,8 @@ and apply this tagger to a tracking data file:
 scripts/larvatagger predict <path/to/backend> <tagger-name> <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. `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.
+
 Note: since `TaggingBackends==0.10`, argument `--skip-make-dataset` is default behavior; pass `--make-dataset` instead to enforce the former default.
 
 To run `larvatagger predict` in parallel on multiple data files using the same tagger, append the `--data-isolation` argument to avoid data conflicts.
diff --git a/recipes/Dockerfile b/recipes/Dockerfile
index a8feb6caef384265c5615b8f83897b4133b24440..3c516f31810aef33875d8fe4b079669760bebb02 100644
--- a/recipes/Dockerfile
+++ b/recipes/Dockerfile
@@ -79,7 +79,7 @@ RUN if [ -z $TAGGINGBACKENDS_BRANCH ]; then \
  && rm -rf .git \
  && poetry install --only main \
  && poetry add "pynvml==11.4.1" \
- && if [ "$(echo $BACKEND | cut -d/ -f2)" = "main" ] || [ "$(echo $BACKEND | cut -d/ -f2)" = "dev" ] || [ "$(echo $BACKEND | cut -d/ -f2)" = "debug" ]; then \
+ && if [ "$(echo $BACKEND | cut -d/ -f2)" = "main" ] || [ "$(echo $BACKEND | cut -d/ -f2)" = "dev" ]; then \
     julia -e 'using Pkg; Pkg.add("JSON3")' \
  && scripts/make_models.jl default; \
     fi \
@@ -88,3 +88,15 @@ RUN if [ -z $TAGGINGBACKENDS_BRANCH ]; then \
 
 COPY recipes/checkgpu /bin/
 
+
+FROM backend AS confusion
+
+ARG PYTHON_ENV=/app/MaggotUBA
+
+RUN test -d $PYTHON_ENV \
+ && cd $PYTHON_ENV \
+ && poetry add "scikit-learn==1.3.0" \
+ && rm -rf ~/.cache
+
+ADD https://gitlab.pasteur.fr/nyx/TaggingBackends/-/raw/dev/scripts/confusion.py?ref_type=heads&inline=false /bin/confusion.py
+
diff --git a/recipes/README.md b/recipes/README.md
index 99c3b8945187075c7e5a48070add3f6879e6f7ef..79be90e707a866ee1f8d1ebf945bf74ee65c8719 100644
--- a/recipes/README.md
+++ b/recipes/README.md
@@ -171,7 +171,7 @@ docker pull flaur/larvatagger
 ```
 
 Beware that images that ship with backends are relatively large files (>5GB on disk).
-If you are not interested in automatic tagging, use the `flaur/larvatagger:0.16.2-standalone` image instead.
+If you are not interested in automatic tagging, use the `flaur/larvatagger:0.17-standalone` image instead.
 
 ### Upgrading
 
diff --git a/scripts/larvatagger b/scripts/larvatagger
index 4c7779ab51cb86d2beac1f77e6875cf3c835b8b0..d9a475be6a19e7f2cab8feccfc1feea31bd87bf4 100755
--- a/scripts/larvatagger
+++ b/scripts/larvatagger
@@ -29,36 +29,40 @@ import|merge|train|predict|--version|-V)
 LarvaTagger
 
 Usage:
-  larvatagger open <file-path> [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--manual-label=<label>] [--segment=<t0,t1>]
+  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>]
-  larvatagger train <backend-path> <data-path> <model-instance> --fine-tune=<instance> [--balancing-strategy=<strategy>] [--manual-label=<label>] [--iterations=<N>] [--seed=<seed>]
-  larvatagger predict <backend-path> <model-instance> <data-path> [--output=<filename>] [--make-dataset] [--skip-make-dataset] [--data-isolation]
+  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]
+  larvatagger predict <backend-path> <model-instance> <data-path> [--output=<filename>] [--make-dataset] [--skip-make-dataset] [--data-isolation] [--debug]
+  larvatagger predict <backend-path> <model-instance> <data-path> --embeddings [--data-isolation] [--debug]
   larvatagger merge <input-path> <input-file> [<output-file>] [--manual-label=<label>] [--decode]
   larvatagger -V | --version
   larvatagger -h | --help
 
 Options:
-  -h --help            Show this screen.
-  -V --version         Show version.
-  -q --quiet           Do not show instructions.
-  --id=<id>            Run or assay ID, e.g. `date_time`.
-  --framerate=<fps>    Camera frame rate, in frames per second.
-  --pixelsize=<μm>     Camera pixel size, in micrometers.
-  --backends=<path>    Path to backend repository.
-  --port=<number>      Port number the server listens to.
-  --viewer             Disable editing capabilities.
-  --browser            Automatically open a browser tab at the served location.
-  --make-dataset       Perform the make_dataset stage prior to proceeding to predict_model.
-  --skip-make-dataset  Skip the make_dataset stage (default behavior since TaggingBackends==0.10).
-  --data-isolation     Isolate the backend data directories for parallel tagging of multiple data files.
-  --sample-size=<N>    Sample only N track segments from the data repository.
-  --layers=<N>         (MaggotUBA) Number of layers of the classifier.
-  --iterations=<N>     (MaggotUBA) Number of training iterations (can be two integers separated by a comma).
-  --seed=<seed>        Seed for the backend's random number generators.
-  --segment=<t0,t1>    Start and end times (included, comma-separated) for cropping and including tracks.
-  --decode             Do not encode the labels into integer indices.
-  --copy-labels        Replicate discrete behavior data from the input file.
+  -h --help             Show this screen.
+  -V --version          Show version.
+  -q --quiet            Do not show instructions.
+  --id=<id>             Run or assay ID, e.g. `date_time`.
+  --framerate=<fps>     Camera frame rate, in frames per second.
+  --pixelsize=<μm>      Camera pixel size, in micrometers.
+  --backends=<path>     Path to backend repository.
+  --port=<number>       Port number the server listens to.
+  --viewer              Disable editing capabilities.
+  --browser             Automatically open a browser tab at the served location.
+  --view-factor=<real>  Scaling factor for the larva views; default is 2 on macOS, 1 elsewhere.
+  --make-dataset        Perform the make_dataset stage prior to proceeding to predict_model.
+  --skip-make-dataset   Skip the make_dataset stage (default behavior since TaggingBackends==0.10).
+  --data-isolation      Isolate the backend data directories for parallel tagging of multiple data files.
+  --sample-size=<N>     Sample only N track segments from the data repository.
+  --layers=<N>          (MaggotUBA) Number of layers of the classifier.
+  --iterations=<N>      (MaggotUBA) Number of training iterations (can be two integers separated by a comma).
+  --seed=<seed>         Seed for the backend's random number generators.
+  --segment=<t0,t1>     Start and end times (included, comma-separated) for cropping and including tracks.
+  --debug               Lower the logging level to DEBUG.
+  --embeddings          (MaggotUBA) Call the backend to generate embeddings instead of labels.
+  --decode              Do not encode the labels into integer indices.
+  --copy-labels         Replicate discrete behavior data from the input file.
   --default-label=<label>             Label all untagged data as <label>.
   --manual-label=<label>              Secondary label for manually labelled data [default: edited].
   --labels=<comma-separated-list>     Comma-separated list of behavior tags/labels.
diff --git a/scripts/larvatagger.sh b/scripts/larvatagger.sh
index 885c5dc78adde2425b413ce922d42558a509ad0b..3222537602cecfdee7015b6f0c38f64c21d026b6 100755
--- a/scripts/larvatagger.sh
+++ b/scripts/larvatagger.sh
@@ -2,7 +2,7 @@
 
 for _ in $(seq $#); do
   case $1 in
-    open|import|merge|train|predict|-V|--version|--more-help|reverse-mapping)
+    open|import|merge|train|predict|-V|--version|--more-help|reverse-mapping|confusion)
       cmd=$1
       shift
       break
@@ -29,6 +29,10 @@ for _ in $(seq $#); do
       if [ "$1" = "--no-cache" ]; then
         # --no-cache is default for build since 0.16.1
         no_cache=1
+      elif [ "$1" = "--target" ]; then
+        DOCKER_ARGS="${DOCKER_ARGS}$1 "
+        shift
+        target=1
       fi
       # note: if DOCKER_ARGS is externally defined, it must end with an explicit space
       DOCKER_ARGS="${DOCKER_ARGS}$1 "
@@ -95,7 +99,9 @@ done
 if [ -z "$no_cache" ] && [ -z "$cache" ]; then
   DOCKER_ARGS="--no-cache $DOCKER_ARGS"
 fi
+if [ -z "$target" ]; then
 DOCKER_ARGS="--target $TARGET $DOCKER_ARGS"
+fi
 if [ -z "$DOCKERFILE" ]; then
   DOCKERFILE=recipes/Dockerfile
 fi
@@ -296,6 +302,22 @@ tagger="20230311"
 DOCKER_RUN="$docker run $DOCKER_ARGS$RUN_ARGS -it --entrypoint julia \"$LARVATAGGER_IMAGE\" \"/app/$backend/scripts/revert_label_mapping.jl\"  \"/data/$output_labels\" \"/data/$unmapped_labels\" \"/data/$edited_labels\" \"/app/$backend/models/$tagger/clf_config.json\""
 echo $DOCKER_RUN
 eval $DOCKER_RUN
+;;
+
+  confusion)
+
+parentdir=$(cd "$1"; pwd -P)
+shift
+
+RUN_ARGS="$RUN_ARGS -v \"$parentdir\":/data"
+
+# wherever taggingbackends (python) is installed
+backend=MaggotUBA
+
+DOCKER_RUN="$docker run $DOCKER_ARGS$RUN_ARGS -it -w /app/$backend --entrypoint='[\"poetry\", \"run\", \"python\"]' \"$LARVATAGGER_IMAGE\" /bin/confusion.py"
+echo "The confusion.py script is shipped only in images built with argument --target confusion"
+echo $DOCKER_RUN
+eval $DOCKER_RUN
 ;;
 
 	--more-help)
@@ -324,16 +346,37 @@ $docker pull ${DOCKER_ARGS}flaur/larvatagger:latest
 	*)
 
 cat << EOT
+Wrapper script to operate larvatagger.jl in a Docker image.
+
 Usage: $0 build [--stable] [--with-default-backend] [--with-backend <backend>]
        $0 open <filepath> [...]
        $0 import <filepath> [<outputfilename>] [...]
        $0 train <datarepository> <taggername> [--backend <name>] [...]
        $0 predict <datafile> [--backend <name>] [--model-instance <taggername>] [...]
+       $0 confusion <datarepository>
        $0 merge <filepath> [<outputfilename>] [...]
+       $0 reverse-mapping <filepath> <filename> <outputfilename>
        $0 --more-help
        $0 --version
        $0 --update
-See --more-help for more information about additional options [...] to larvatagger.jl
+
+Note the arguments are very similar to those of larvatagger.jl, except with the train
+command. Indeed, the backend is MaggotUBA-adapter per default, if included in the image.
+
+The build, confusion and reverse-mapping commands are specific to the present script and
+do not interface with larvatagger.jl.
+
+The confusion command crawls the data repository (first argument) in search for
+groundtruth.label and predicted.label files, and generates a confusion.csv file wherever
+both label files are found.
+
+The reverse-mapping command takes two sibling label files, the first one with mapped labels,
+the second one with unmapped labels. It generates a third label file with demapped labels
+from the first file. This is useful when the first file diverges from the second one by some
+manual editions, on top of label mapping.
+
+See --more-help for more information about additional arguments for the other commands from
+larvatagger.jl.
 EOT
 ;;
 
diff --git a/src/Taggers.jl b/src/Taggers.jl
index 38f57c2e1683e57a49b83f3da2c3bf66744e678c..f75bb50625b14544dbda737e13752865f20c4d0d 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -2,7 +2,7 @@ module Taggers
 
 import PlanarLarvae.Formats, PlanarLarvae.Dataloaders
 
-export Tagger, isbackend, resetmodel, resetdata, train, predict, finetune
+export Tagger, isbackend, resetmodel, resetdata, train, predict, finetune, embed
 
 struct Tagger
     backend_dir::String
@@ -247,4 +247,14 @@ function finetune(tagger::Tagger; original_instance=nothing, kwargs...)
     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
+
 end # module
diff --git a/src/cli.jl b/src/cli.jl
index ab9a0341feff42dd394f059febff75c9314090e4..c0a25fbc05090bc4e0ee55eb5c23388c8b7e62d6 100644
--- a/src/cli.jl
+++ b/src/cli.jl
@@ -9,39 +9,47 @@ using .Toolkit
 usage = """Larva Tagger.
 
 Usage:
-  larvatagger.jl open <file-path> [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--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]
-  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>]
-  larvatagger.jl predict <backend-path> <model-instance> <data-path> [--output=<filename>] [--make-dataset] [--skip-make-dataset] [--data-isolation]
+  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]
+  larvatagger.jl predict <backend-path> <model-instance> <data-path> [--output=<filename>] [--make-dataset] [--skip-make-dataset] [--data-isolation] [--debug]
+  larvatagger.jl predict <backend-path> <model-instance> <data-path> --embeddings [--data-isolation] [--debug]
   larvatagger.jl merge <input-path> <input-file> [<output-file>] [--manual-label=<label>] [--decode]
   larvatagger.jl -V | --version
   larvatagger.jl -h | --help
 
 Options:
-  -h --help            Show this screen.
-  -V --version         Show version.
-  -q --quiet           Do not show instructions.
-  --id=<id>            Run or assay ID, e.g. `date_time`.
-  --framerate=<fps>    Camera frame rate, in frames per second.
-  --pixelsize=<μm>     Camera pixel size, in micrometers.
-  --backends=<path>    Path to backend repository.
-  --port=<number>      Port number the server listens to.
-  --viewer             Disable editing capabilities.
-  --browser            Automatically open a browser tab at the served location.
-  --make-dataset       Perform the make_dataset stage prior to proceeding to predict_model.
-  --skip-make-dataset  Skip the make_dataset stage (default behavior since TaggingBackends==0.10).
-  --data-isolation     Isolate the backend data directories for parallel tagging of multiple data files.
-  --sample-size=<N>    Sample only N track segments from the data repository.
-  --layers=<N>         (MaggotUBA) Number of layers of the classifier.
-  --iterations=<N>     (MaggotUBA) Number of training iterations (can be two integers separated by a comma).
-  --seed=<seed>        Seed for the backend's random number generators.
-  --decode             Do not encode the labels into integer indices.
+  -h --help             Show this screen.
+  -V --version          Show version.
+  -q --quiet            Do not show instructions.
+  --id=<id>             Run or assay ID, e.g. `date_time`.
+  --framerate=<fps>     Camera frame rate, in frames per second.
+  --pixelsize=<μm>      Camera pixel size, in micrometers.
+  --backends=<path>     Path to backend repository.
+  --port=<number>       Port number the server listens to.
+  --viewer              Disable editing capabilities.
+  --browser             Automatically open a browser tab at the served location.
+  --view-factor=<real>  Scaling factor for the larva views; default is 2 on macOS, 1 elsewhere.
+  --make-dataset        Perform the make_dataset stage prior to proceeding to predict_model.
+  --skip-make-dataset   Skip the make_dataset stage (default behavior since TaggingBackends==0.10).
+  --data-isolation      Isolate the backend data directories for parallel tagging of multiple data files.
+  --sample-size=<N>     Sample only N track segments from the data repository.
+  --layers=<N>          (MaggotUBA) Number of layers of the classifier.
+  --iterations=<N>      (MaggotUBA) Number of training iterations (can be two integers separated by a comma).
+  --seed=<seed>         Seed for the backend's random number generators.
+  --segment=<t0,t1>     Start and end times (included, comma-separated) for cropping and including tracks.
+  --debug               Lower the logging level to DEBUG.
+  --embeddings          (MaggotUBA) Call the backend to generate embeddings instead of labels.
+  --decode              Do not encode the labels into integer indices.
+  --copy-labels         Replicate discrete behavior data from the input file.
   --default-label=<label>             Label all untagged data as <label>.
   --manual-label=<label>              Secondary label for manually labelled data [default: edited].
   --labels=<comma-separated-list>     Comma-separated list of behavior tags/labels.
   --class-weights=<csv>               Comma-separated list of floats.
   --pretrained-model=<instance>       Name of the pretrained encoder (from `pretrained_models` registry).
   --balancing-strategy=<strategy>     Any of `auto`, `maggotuba`, `none` [default: auto].
+  --fine-tune=<instance>              Load and fine-tune an already trained model.
   --overrides=<comma-separated-list>  Comma-separated list of key:value pairs.
   -o <filename> --output=<filename>   Predicted labels filename.
 
diff --git a/src/cli_open.jl b/src/cli_open.jl
index 023b432a6a9c20d9bfb4657c44b6cabcf268ee97..d62d3740648b993fe875adc55966d5bc49f32709 100644
--- a/src/cli_open.jl
+++ b/src/cli_open.jl
@@ -9,7 +9,7 @@ export main
 usage = """LarvaTagger.jl - launch the server-based GUI.
 
 Usage:
-  larvatagger-gui.jl <file-path> [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--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:
@@ -19,6 +19,7 @@ Options:
   --port=<number>         Port number the server listens to.
   --viewer                Disable editing capabilities.
   --browser               Automatically open a browser tab at the served location.
+  --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].
 
 Backends defined in LarvaTagger project root directory are automatically found. Other
@@ -50,10 +51,18 @@ function main(args=ARGS; exit_on_error=false)
         @error "File not found; did you specify a file path?" infile
         exit()
     end
+
+    kwargs = Dict{Symbol, Any}()
+    viewfactor = parsed_args["--view-factor"]
+    if !isnothing(viewfactor)
+        kwargs[:viewfactor] = parse(Float64, viewfactor)
+    elseif Sys.isapple()
+        kwargs[:viewfactor] = 2
+    end
+
     if parsed_args["--viewer"]
-        app = larvaviewer(infile)
+        app = larvaviewer(infile; kwargs...)
     else
-        kwargs = Dict{Symbol, Any}()
         backends = parsed_args["--backends"]
         if !isnothing(backends)
             kwargs[:backend_directory] = backends
diff --git a/src/cli_toolkit.jl b/src/cli_toolkit.jl
index 82a00be34159ba3519c59d0131a40f663ff5c5b0..5614735f43da6693ad420b0a416c32c74792c9b3 100644
--- a/src/cli_toolkit.jl
+++ b/src/cli_toolkit.jl
@@ -16,9 +16,10 @@ usage = """Larva Tagger.
 
 Usage:
   larvatagger-toolkit.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] [--segment=<t0,t1>]
-  larvatagger-toolkit.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>]
-  larvatagger-toolkit.jl train <backend-path> <data-path> <model-instance> --fine-tune=<instance> [--balancing-strategy=<strategy>] [--manual-label=<label>] [--iterations=<N>] [--seed=<seed>]
-  larvatagger-toolkit.jl predict <backend-path> <model-instance> <data-path> [--output=<filename>] [--make-dataset] [--skip-make-dataset] [--data-isolation]
+  larvatagger-toolkit.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-toolkit.jl train <backend-path> <data-path> <model-instance> --fine-tune=<instance> [--balancing-strategy=<strategy>] [--manual-label=<label>] [--iterations=<N>] [--seed=<seed>] [--debug]
+  larvatagger-toolkit.jl predict <backend-path> <model-instance> <data-path> [--output=<filename>] [--make-dataset] [--skip-make-dataset] [--data-isolation] [--debug]
+  larvatagger-toolkit.jl predict <backend-path> <model-instance> <data-path> --embeddings [--data-isolation] [--debug]
   larvatagger-toolkit.jl merge <input-path> <input-file> [<output-file>] [--manual-label=<label>] [--decode]
   larvatagger-toolkit.jl -V | --version
   larvatagger-toolkit.jl -h | --help
@@ -37,6 +38,8 @@ Options:
   --iterations=<N>     Number of training iterations (integer or comma-separated list of integers).
   --seed=<seed>        Seed for the backend's random number generators.
   --segment=<t0,t1>    Start and end times (included, comma-separated) for cropping and including tracks.
+  --debug              Lower the logging level to DEBUG.
+  --embeddings         (MaggotUBA) Call the backend to generate embeddings instead of labels.
   --decode             Do not encode the labels into integer indices.
   --copy-labels        Replicate discrete behavior data from the input file.
   --default-label=<label>             Label all untagged data as <label>.
@@ -161,6 +164,8 @@ function main(args=ARGS; exit_on_error=true)
         isnothing(iterations) || (kwargs[:iterations] = iterations)
         seed = parsed_args["--seed"]
         isnothing(seed) || (kwargs[:seed] = seed)
+        debug = parsed_args["--debug"]
+        isnothing(debug) || (kwargs[:debug] = debug)
         #
         finetune_model = parsed_args["--fine-tune"]
         if isnothing(finetune_model) # standard train
@@ -185,6 +190,7 @@ function main(args=ARGS; exit_on_error=true)
         data_path = parsed_args["<data-path>"]
         data_isolation = parsed_args["--data-isolation"]
         output_filename = parsed_args["--output"]
+        embeddings = parsed_args["--embeddings"]
         #
         datapath = abspath(data_path)
         destination = if isfile(datapath)
@@ -213,8 +219,13 @@ function main(args=ARGS; exit_on_error=true)
         end
         resetdata(tagger)
         Taggers.push(tagger, datapath)
-        predict(tagger; skip_make_dataset=parsed_args["--skip-make-dataset"],
-                make_dataset=parsed_args["--make-dataset"])
+        if embeddings
+            embed(tagger; skip_make_dataset=parsed_args["--skip-make-dataset"],
+                make_dataset=parsed_args["--make-dataset"], debug=parsed_args["--debug"])
+        else
+            predict(tagger; skip_make_dataset=parsed_args["--skip-make-dataset"],
+                make_dataset=parsed_args["--make-dataset"], debug=parsed_args["--debug"])
+        end
         Taggers.pull(tagger, destination)
     end
 end
diff --git a/src/editor.jl b/src/editor.jl
index 07e96b2c76233577df9e2c6e260d88d670d0116b..61c33882a40150683a38145aa3c2ab10f7f9e8c2 100644
--- a/src/editor.jl
+++ b/src/editor.jl
@@ -32,7 +32,8 @@ projectdir = dirname(Base.active_project())
 function larvaeditor(path=nothing;
         allow_multiple_tags::Union{Nothing, Bool}=nothing,
         backend_directory::AbstractString=projectdir,
-        manualtag::Union{Nothing, String, Symbol}="edited")
+        manualtag::Union{Nothing, String, Symbol}="edited",
+        kwargs...)
 
     # to (re-)load a file, the app is reloaded with the filepath as sole information
     # from previous session
@@ -48,7 +49,8 @@ function larvaeditor(path=nothing;
 
         editor = EditorView(larvaviewer(controller;
                                         editabletags=true,
-                                        multipletags=allow_multiple_tags),
+                                        multipletags=allow_multiple_tags,
+                                        kwargs...),
                             larvafilter(controller),
                             tagfilter(controller; manualtag=manualtag),
                             metadataeditor(controller),
diff --git a/src/files.jl b/src/files.jl
index bde0c9c763a295ed54564ac2790c1143c96661da..ac616586b19d9fde077cc3b1eb3ae5568ae7933f 100644
--- a/src/files.jl
+++ b/src/files.jl
@@ -206,6 +206,10 @@ function loadfile(path)
     if file isa Formats.FIMTrack
         times = PlanarLarvae.times(data)
         tracks = [LarvaModel(track, times) for track in values(getrun(file))]
+    elseif file isa Formats.MaggotUBA
+        times = PlanarLarvae.times(data)
+        tracks = Formats.astimeseries(getrun(file); labels2tags=true)
+        tracks = [LarvaModel(id, ts, times) for (id, ts) in tracks]
     else
         times = PlanarLarvae.times(gettimeseries(file))
         tracks = [LarvaModel(id, ts, times) for (id, ts) in pairs(gettimeseries(file))]
@@ -409,7 +413,7 @@ function explicit_editions_needed(controller, editiontag)
     if file isa Formats.JSONLabels
         Formats.load!(file)
         return haskey(file.run.attributes, :labels)
-    elseif file isa Formats.Trxmat
+    elseif file isa Formats.Trxmat || file isa MaggotUBA
         return true
     else
         return false
diff --git a/src/models.jl b/src/models.jl
index 965fd4e325f7517e0dff96adebba3a0e6c8855fb..2c537415dc328090bbf2133ac7e5b982be09c88c 100644
--- a/src/models.jl
+++ b/src/models.jl
@@ -109,9 +109,9 @@ end
 function getusertags(larva, timestep; lut=nothing)
     usertags = larva.usertags[]
     tags = UserTags()
-    try
+    if haskey(usertags, timestep)
         tags = usertags[timestep]
-    catch
+    else
         firststep = larva.alignedsteps[1]
         firststep <= timestep || return tags
         relstep = timestep - firststep + 1
@@ -250,7 +250,6 @@ function LarvaModel(track::Track, times::Vector{PlanarLarvae.Time})
     missingsteps = ones(Bool, steps[end])
     missingsteps[steps] .= false
     missingsteps = findall(missingsteps)
-    # this method may be dead code; the warning below has never been emitted
     isempty(missingsteps) || @warn "Time steps are missing" id=convert(Int, track.id) missingsteps
     path = coordinates.(larvatrack(track[:spine]))
     usertags = Observable(Dict{TimeStep, UserTags}())
@@ -262,7 +261,28 @@ function LarvaModel(track::Track, times::Vector{PlanarLarvae.Time})
                usertags)
 end
 
-Meshes.boundingbox(larva::LarvaModel) = Meshes.boundingbox([outline(state) for (_,state) in larva.fullstates])
+"""
+    outline_or_spine(state)
+    outline_or_spine(pointtype, state)
+
+Return outline data if available, spine data otherwise.
+"""
+function outline_or_spine(state)
+    if haskey(state, :outline)
+        outline(state)
+    else
+        spine(state)
+    end
+end
+function outline_or_spine(T, state)
+    if haskey(state, :outline)
+        outline(T, state)
+    else
+        spine(T, state)
+    end
+end
+
+Meshes.boundingbox(larva::LarvaModel) = Meshes.boundingbox([outline_or_spine(state) for (_,state) in larva.fullstates])
 Meshes.boundingbox(larvae::Vector{LarvaModel}) = Meshes.boundingbox(map(Meshes.boundingbox, larvae))
 
 function downsampler(; step::Int=20)
diff --git a/src/players.jl b/src/players.jl
index f805cf91b922e61d7bd765e922ab158bfcfc035b..ff1f5fd5ae8ed5fa98a213c2aee584ace3fc1903 100644
--- a/src/players.jl
+++ b/src/players.jl
@@ -158,6 +158,7 @@ function timecontroller(times::Vector{Float64}; speed=1.0)
     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[]
@@ -177,7 +178,8 @@ function timecontroller(times::Vector{Float64}; speed=1.0)
         end
     end
     on(boundreached) do b
-        b && @info "Time bound reached"
+        b && initialized[] && @info "Time bound reached"
+        initialized[] = true
     end
     on(stepmin) do bound
         0 < bound || throw(DomainError("stepmin < 1"))
diff --git a/src/plots.jl b/src/plots.jl
index c5b21a867310f223a32dd1829202ae6012cfda38..0364c82020fdb99104df2c9c5f37025810f5b481 100644
--- a/src/plots.jl
+++ b/src/plots.jl
@@ -13,7 +13,7 @@ end
 
 #
 
-const CompatLarvaID = Int16
+const CompatLarvaID = Int32
 const ActiveLarva = Union{Nothing, CompatLarvaID}
 
 function withalpha(outline_color, alpha_value)
@@ -82,7 +82,7 @@ function StatefulLarva(larva::LarvaModel,
     _, color = gettag(tag_lut, larva, larva.alignedsteps[1], fallback_color)
 
     # key observables
-    shape_outline = Observable(outline(Makie.Point2f, state))
+    shape_outline = Observable(outline_or_spine(Makie.Point2f, state))
     shape_color = Observable(html_color(color))
     visibility = Observable(false)
 
@@ -96,7 +96,7 @@ function StatefulLarva(larva::LarvaModel,
             end
             step -= count(larva.missingsteps .< step)
             _, state = timeseries[step]
-            shape_outline.val = outline(Makie.Point2f, state)
+            shape_outline.val = outline_or_spine(Makie.Point2f, state)
             #
             _, color = gettag(tag_lut, larva, timestep, fallback_color)
             shape_color.val = html_color(color)
@@ -148,7 +148,7 @@ function SingleLarvaView(larvae::Vector{LarvaModel}, controller; editabletags::B
     visible = Observable(false)
     path = Observable(larva.path)
     pathtree = map(KDTree, path)
-    shape_outline = Observable(outline(Makie.Point2f, state))
+    shape_outline = Observable(outline_or_spine(Makie.Point2f, state))
     shape_color = Observable(html_color(color))
 
     # TODO: move the callbacks to `plot!`
@@ -201,7 +201,7 @@ function SingleLarvaView(larvae::Vector{LarvaModel}, controller; editabletags::B
            )
             step -= count(larva.missingsteps .< step)
             _, state = larva.fullstates[step]
-            shape_outline.val = outline(Makie.Point2f, state)
+            shape_outline.val = outline_or_spine(Makie.Point2f, state)
             #
             _, color = gettag(tag_lut, larva, timestep, fallback_color)
             shape_color.val = html_color(color)
@@ -593,7 +593,7 @@ function DecoratedLarvae(larvae::Vector{DecoratedLarva})
             else
                 @error begin
                     j_id = larvae[j].larva.model.id
-                    "cannot decorate invisible larva #$(j_id) - please file an issue at https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/issues"
+                    "Cannot decorate invisible larva #$(j_id)"
                 end
                 hovered_larva.val = 0
             end
diff --git a/src/viewer.jl b/src/viewer.jl
index 6ea1a4c93e0779f2d77fc86d298de3e60cec76c2..7463492dd4eef02ba03de68d9bd57efd5c12df6a 100644
--- a/src/viewer.jl
+++ b/src/viewer.jl
@@ -42,7 +42,8 @@ function JSServe.jsrender(session::Session, vv::ViewerView)
       DOM.div(JSServe.TailwindCSS, assaydom, trackdom; class="flex flex-row"))
 end
 
-function larvaviewer(path::String; allow_multiple_tags::Union{Nothing, Bool}=false)
+function larvaviewer(path::String; allow_multiple_tags::Union{Nothing, Bool}=false,
+    kwargs...)
 
     App() do session::Session
 
@@ -50,7 +51,7 @@ function larvaviewer(path::String; allow_multiple_tags::Union{Nothing, Bool}=fal
 
         tryopenfile(controller, path)
 
-        viewer = larvaviewer(controller; multipletags=allow_multiple_tags)
+        viewer = larvaviewer(controller; multipletags=allow_multiple_tags, kwargs...)
 
         r(session, LarvaTaggerJS)
         r(session, LarvaTaggerCSS)
@@ -62,6 +63,7 @@ end
 function larvaviewer(controller;
         editabletags::Bool=false,
         multipletags::Union{Nothing, Bool}=false,
+        viewfactor::Real=1,
     )
     model = getlarvae(controller)
     times = gettimes(controller)
@@ -85,10 +87,19 @@ function larvaviewer(controller;
         @info isnothing(id) ? "No active larva" : "Activating larva #$(id)"
     end
 
-    viewer = ViewerView(assayviewer(delayed_controller),
+    kwargs = Dict{Symbol, Any}()
+    if viewfactor != 1
+        width, height = FIGSIZE
+        width = round(Int, width * viewfactor)
+        height = round(Int, height * viewfactor)
+        kwargs[:size] = (width, height)
+    end
+
+    viewer = ViewerView(assayviewer(delayed_controller; kwargs...),
                         trackviewer(controller;
                                     editabletags=editabletags,
-                                    multipletags=multipletags))
+                                    multipletags=multipletags,
+                                    kwargs...))
 
     settimestep!(controller, 1)
 
diff --git a/src/wgl.jl b/src/wgl.jl
index c9cad2c70a4623d433ca57ff2afb8b4ac616f8d9..164fe6792251376c606e2a5ef7e82479807772a3 100644
--- a/src/wgl.jl
+++ b/src/wgl.jl
@@ -118,6 +118,7 @@ struct AssayPlot
 end
 
 function AssayPlot(ctrl, larvae::DecoratedLarvae; size=FIGSIZE)
+    gethub(ctrl)[:decoratedlarvae] = larvae
     fig = Figure(resolution=size)
     width = 0.1f0 # try to get 1px
     color = RGBAf(0, 0, 0, 0.36)
@@ -421,10 +422,10 @@ mutable struct AssayViewer
     dom
 end
 
-function assayviewer(controller)
+function assayviewer(controller; kwargs...)
     model = getlarvae(controller)
     visible = Observable(true)
-    plot = assayplot(model, controller, visible)
+    plot = assayplot(model, controller, visible; kwargs...)
     play = player(controller)
     on(visible) do b
         if b
@@ -448,14 +449,50 @@ end
 struct LarvaInfo
     controller
     id::LarvaID
+    hovered::AbstractObservable{Bool}
+    clicked::AbstractObservable{Bool}
     reviewed::AbstractObservable{Bool}
     edited::AbstractObservable{Bool}
     included::AbstractObservable{Bool}
 end
 
 function larvainfo(controller, id)
+    hovered = Observable(false)
+    clicked = Observable(false)
     reviewed = Observable(false)
     edited = Observable(false)
+    #
+    larvae = gethub(controller)[:decoratedlarvae]
+    larvaindex = nothing
+    for (i, larva) in enumerate(larvae.larvae)
+        larva = larva.larva
+        if larva.model.id == id
+            larvaindex = i
+        end
+    end
+    larvavisible = larvae.larvae[larvaindex].larva.visible
+    on(hovered) do b
+        if larvae.hovering_active[]
+            if b
+                if larvavisible[]
+                    larvae.hovered_larva[] = larvaindex
+                end
+            else
+                i = larvae.hovered_larva[]
+                if i != 0
+                    # TODO: additionally condition the below warning on the type of larva
+                    #       view (in AssayPlot, warn; in TrackPlot, do not warn)
+                    #i == larvaindex || @warn "Larva #$(larvae.larvae[i].larva.model.id) unexpectedly decorated"
+                    larvae.hovered_larva[] = 0
+                end
+            end
+        end
+    end
+    on(clicked) do b
+        @assert b
+        activatelarva!(controller, id)
+        clicked.val = false
+    end
     on(edited) do b
         if b
             #@assert reviewed[]
@@ -474,6 +511,8 @@ function larvainfo(controller, id)
     included = Observable(false)
     LarvaInfo(controller,
               id,
+              hovered,
+              clicked,
               reviewed,
               edited,
               included)
@@ -482,7 +521,10 @@ end
 JSServe.jsrender(session::Session, li::LarvaInfo) = r(session, prerender(li))
 
 function prerender(li::LarvaInfo)
-    label = "#$(li.id)"
+    label = DOM.label("#$(li.id)",
+                      onmouseenter=js"JSServe.update_obs($(li.hovered), true)",
+                      onmouseleave=js"JSServe.update_obs($(li.hovered), false)",
+                      onmouseup=js"JSServe.update_obs($(li.clicked), true)")
     discard_larva_edits = js"LarvaTagger.discardLarvaEdits(this, $(li.edited), $label)"
     reviewed_checkbox = DOM.input(type="checkbox",
                                   checked=li.reviewed,
@@ -501,8 +543,7 @@ function prerender(li::LarvaInfo)
     DOM.tr(DOM.td(label),
            DOM.td(reviewed_checkbox, style="text-align: center;"),
            DOM.td(edited_checkbox, style="text-align: center;"),
-           DOM.td(included_checkbox, style="text-align: center;"),
-          )
+           DOM.td(included_checkbox, style="text-align: center;"))
 end
 
 struct LarvaFilter
@@ -990,9 +1031,10 @@ end
 function trackviewer(controller;
         editabletags::Bool=true,
         multipletags::Union{Nothing, Bool}=nothing,
+        kwargs...
     )
     model = getlarvae(controller)
-    plot = trackplot(model, controller; editabletags=editabletags)
+    plot = trackplot(model, controller; editabletags=editabletags, kwargs...)
     play = player(controller)
     sele = tagselector(controller; editabletags=editabletags, multipletags=multipletags)
     TrackViewer(plot, play, sele, nothing)
diff --git a/test/deploy_and_test.sh b/test/deploy_and_test.sh
index 71e3406660068220d960b8a8175a82e5cb94a681..4d3ca51c7c3f1c59d122a296b6a3df53a4ea53f6 100755
--- a/test/deploy_and_test.sh
+++ b/test/deploy_and_test.sh
@@ -103,7 +103,7 @@ if [ -f ../../LarvaTagger_test_data.tgz ]; then
   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/l3qruSQR/LarvaTagger_test_data.tgz | tar zxv
+  wget -O- https://dl.pasteur.fr/fop/8MvgygD4/LarvaTagger_test_data.tgz | tar zxv
 fi
 
 if [ "$LOCAL_SCENARII" = "1" ]; then
diff --git a/test/scenarii.sh b/test/scenarii.sh
index 59a96d49a14ad72fc414f258b986496c9cd975a9..cfe2784ad504372ec4396e644b400137203a0839 100755
--- a/test/scenarii.sh
+++ b/test/scenarii.sh
@@ -44,6 +44,10 @@ maggotuba="../MaggotUBA"
 
 seed=1028347112001
 
+endTest() {
+  echo "----------------------------------------------------------------------------"
+}
+
 prepareTestData() {
   tmpdir="$SHUNIT_TMPDIR/$1"
   rm -rf "$tmpdir"
@@ -79,6 +83,7 @@ testImportLabelFile() {
   assertTrue '\`import\` failed to reproduce the imported.label file' '$(cmp "$datapath/imported.label" "$datapath/$filename")'
   # clean up
   rm -f "$datapath/$filename"
+  endTest
 }
 
 # requires: cropped.label
@@ -92,6 +97,7 @@ testCropTracks() {
   assertTrue '\`import\` failed to reproduce the cropped.label file' '$(cmp "$datapath/cropped.label" "$datapath/$filename")'
   # clean up
   rm -f "$datapath/$filename"
+  endTest
 }
 
 # requires: sample.spine sample.outline test_train_default/predicted.label
@@ -106,14 +112,15 @@ testPredictDefault() {
   [ -f "$maggotuba/models/$tagger/clf_config.json" ] || exit 1
   # run
   cd "$project_root"
-  echo "\"$larvataggerjl\" predict \"$maggotuba\" $tagger \"$tmpdir/sample.spine\""
-  "$larvataggerjl" predict "$maggotuba" $tagger "$tmpdir/sample.spine"
+  echo "\"$larvataggerjl\" predict \"$maggotuba\" $tagger \"$tmpdir/sample.spine\" --debug"
+  "$larvataggerjl" predict "$maggotuba" $tagger "$tmpdir/sample.spine" --debug
   # compare
   filename=predicted.label
   predictions="$tmpdir/$filename"
   expected_labels="$datapath/$tagger/$filename"
   assertFalse "\`predict\` failed to generate file $filename" '[ -z "$predictions" ]'
   assertTrue "\`predict\` failed to reproduce file $filename" '$(cmp "$expected_labels" "$predictions")'
+  endTest
 }
 
 # requires: sample.spine sample.outline original_predictions.label test_train_default/*
@@ -122,10 +129,11 @@ testTrainDefault() {
   tmpdir=$(prepareTrainingData original_predictions.label $tagger)
   # run
   cd "$project_root"
-  echo "\"$larvataggerjl\" train \"$maggotuba\" \"$tmpdir\" $tagger --seed $seed"
-  "$larvataggerjl" train "$maggotuba" "$tmpdir" $tagger --seed $seed
+  echo "\"$larvataggerjl\" train \"$maggotuba\" \"$tmpdir\" $tagger --seed $seed --debug"
+  "$larvataggerjl" train "$maggotuba" "$tmpdir" $tagger --seed $seed --debug
   # test
   postTrain $tagger
+  endTest
 }
 
 # requires: sample.spine sample.outline imported.label test_train_one_class/*
@@ -138,6 +146,7 @@ testTrainOneClass() {
   "$larvataggerjl" train "$maggotuba" "$tmpdir" $tagger --iterations 10 --seed $seed --labels="back-up,not back-up"
   # test
   postTrain $tagger
+  endTest
 }
 
 # requires: sample.spine sample.outline imported.label test_train_one_class_with_weights/*
@@ -150,6 +159,7 @@ testTrainOneClassWithWeights() {
   "$larvataggerjl" train "$maggotuba" "$tmpdir" $tagger --iterations 10 --seed $seed --labels="not back-up,back-up" --class-weights 1,10
   # test
   postTrain $tagger
+  endTest
 }
 
 # requires: sample.spine sample.outline gui_imported.label test_train_one_class_with_encoder/*
@@ -162,6 +172,7 @@ testTrainOneClassWithEncoder() {
   "$larvataggerjl" train "$maggotuba" "$tmpdir" $tagger --iterations 10 --seed $seed --labels="hunch,¬hunch" --pretrained-model=20230524-hunch-25 --balancing-strategy=maggotuba
   # test
   postTrain $tagger
+  endTest
 }
 
 # requires: sample.spine sample.outline trx.mat gui_imported.label original_predictions.label test_train_selected_files/*
@@ -178,6 +189,7 @@ testTrainSelectedFiles() {
   # test
   cd "$project_root"
   postTrain $tagger
+  endTest
 }
 
 # requires: sample.spine sample.outline trx.mat gui_imported.label original_predictions.label test_train_recursive_selection/*
@@ -191,6 +203,7 @@ testTrainRecursiveSelection() {
   "$larvataggerjl" train "$maggotuba" "$tmpdir/**/gui_imported.label" $tagger --seed $seed --labels="run_large,cast_large,hunch_large" --balancing-strategy=maggotuba --iterations=10
   # test
   postTrain $tagger
+  endTest
 }
 
 postTrain() {