Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cdb3a56
Rewrite vision label_tool with workspace-backed GUI
SimoneMartino98 Jul 16, 2026
281a7a9
Add progress bars, logo and explicit session save to label_tool
SimoneMartino98 Jul 17, 2026
daac8f7
Make justfile work with both uv and conda
SimoneMartino98 Jul 17, 2026
dd84252
Fix mypy errors in lens and vision
SimoneMartino98 Jul 17, 2026
f5762be
Fix just check reporting success on failure, and package import
SimoneMartino98 Aug 27, 2026
65c25a1
Support a ./venv environment alongside ./.venv
SimoneMartino98 Aug 27, 2026
6b2668b
Ignore the label_tool workspace directory
SimoneMartino98 Aug 27, 2026
494758a
Fix ruff 0.16 failures and keep a ./venv out of mypy
SimoneMartino98 Aug 27, 2026
0e1a97e
Support name-less .xyz files in track_xyz
SimoneMartino98 Aug 27, 2026
0350bf6
Rewrite vision label_tool with workspace-backed GUI
SimoneMartino98 Jul 16, 2026
9f9c43a
Add progress bars, logo and explicit session save to label_tool
SimoneMartino98 Jul 17, 2026
bdee5d5
Make justfile work with both uv and conda
SimoneMartino98 Jul 17, 2026
fd4af2d
Fix mypy errors in lens and vision
SimoneMartino98 Jul 17, 2026
978531e
Fix just check reporting success on failure, and package import
SimoneMartino98 Aug 27, 2026
0ff44da
Support a ./venv environment alongside ./.venv
SimoneMartino98 Aug 27, 2026
9a45e52
Ignore the label_tool workspace directory
SimoneMartino98 Aug 27, 2026
d9c1778
Fix ruff 0.16 failures and keep a ./venv out of mypy
SimoneMartino98 Aug 27, 2026
e6ed8f2
Support name-less .xyz files in track_xyz
SimoneMartino98 Aug 27, 2026
b24224a
Merge branch 'vision/label-tool-rework' of https://github.com/SimoneM…
SimoneMartino98 Aug 27, 2026
30298d2
Fix four bugs in slicing, spatial averaging and tracking
SimoneMartino98 Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ __pycache__
/build
docs/build
docs/source/_autosummary
label_tool_workspace
docs/source/_static/info_plot.png
examples/onion_analysis/data.json
examples/analysis_workflow/lens.json
Expand All @@ -23,4 +24,5 @@ tests/systems/.*
.pytest_cache
.ruff_cache
.venv
venv

Binary file removed docs/source/_static/label_bar.png
Binary file not shown.
Binary file removed docs/source/_static/label_menu.png
Binary file not shown.
Binary file modified docs/source/_static/label_tool.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 50 additions & 21 deletions docs/source/label_tool/label_tool.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
The Label Tool
==============

The ``dynsight label_tool`` is a simple web application that allows users to
label images. Picture labelling is a crucial step in many computer vision tasks,
such as the creation of initial training dataset to train Convolutional Neural
Networks (CNNs) model. The current version of `dynsight vision <../_autosummary/dynsight.vision.VisionInstance.html>`_
The ``dynsight label_tool`` is a local web application for labeling images
and building training datasets. Picture labelling is a crucial step in many
computer vision tasks, such as the creation of the initial dataset used to
train Convolutional Neural Networks (CNNs). The current version of
`dynsight vision <../_autosummary/dynsight.vision.VisionInstance.html>`_
exploits the power of the `YOLO models <https://docs.ultralytics.com/models/yolo12/>`_
for computer vision tasks. Thus, the ``label_tool`` has been specifically
designed to work with the YOLO dataset format.
for computer vision tasks. Thus, the ``label_tool`` writes datasets directly
in the YOLO format expected by
`set_training_dataset <../_autosummary/dynsight.vision.VisionInstance.html#dynsight.vision.VisionInstance.set_training_dataset>`_,
so they can be used for training without any manual editing.

.. image:: ../_static/label_tool.png

Expand All @@ -30,7 +33,10 @@ The ``label_tool`` application can be executed in 2 main ways:

import dynsight

dynsight.vision.label_tool(port=8888) #port selection is optional
dynsight.vision.label_tool(
port=8888, # optional
workspace="my_workspace", # optional
)

In both cases a localhost server should start and the application should
automatically appear in your default web browser.
Expand All @@ -41,30 +47,53 @@ automatically appear in your default web browser.
not open automatically, you can manually open it by copying and pasting
the URL provided in the terminal output.

All uploaded images are stored inside the *workspace* directory
(``./label_tool_workspace`` by default). The labeling session (labels and
boxes) is kept in memory and is **never written to disk automatically**:
use the *Save session* button to write it to a JSON file at a path of your
choice, and *Load* to restore it later. If the session has unsaved changes,
the *Quit* button asks whether to save it before stopping the server
(``Ctrl+C`` in the terminal also stops it).

-------
The GUI
-------

The ``label_tool`` Graphical User Interface is divided in three main panels:
The Graphical User Interface is divided in three main panels:

* **The image panel**: where loaded images appear and labels can be drawn.
* **The labels panel** (top left): create the object classes. Each label
shows its YOLO class ID, its color and the number of boxes drawn with it.
Class IDs follow the order of this list and are stable across exports.

* **The label menu panel**: where labels can be created and edited.
* **The images panel** (bottom left): add content with ``+ Images`` or
``+ Video`` (frames are extracted at a chosen interval), or by dragging
and dropping files onto the canvas. Each entry shows a thumbnail and its
number of annotations.

.. image:: ../_static/label_menu.png
:align: center
* **The canvas** (right): displays the current image and the bounding
boxes.

* **The commands panel**: where all the available commands can be executed.
Annotating is done directly on the canvas:

.. image:: ../_static/label_bar.png
* **Draw**: select a label, then click and drag.
* **Select**: click a box.
* **Move / resize**: drag a selected box, or drag one of its handles.
* **Change label**: select a box, then click a different label.
* **Delete**: right-click a box, or select it and press backspace.
* **Navigate**: mouse wheel to zoom, space (or middle mouse) drag to pan,
arrow keys to switch image.

Using the ``Choose File`` button, users can select the image(s) they want to
label. Once the image is loaded, users can start drawing labels by clicking and
dragging on the image panel. The label menu panel allows users to create and
edit labels. Finally, the commands panel provides a set of exporting options:
Every long operation (image and video uploads, frame extraction, dataset
export and synthesis) shows a progress bar at the bottom of the canvas.

* **Export label**: Download a single ``.txt`` file in YOLO format containing the labels for the current image.
Two export options are available in the top bar. Both write the dataset
folder directly to disk (inside the workspace by default) together with a
ready-to-use ``dataset.yaml``:

* **Export dataset**: Download a YOLO dataset from the loaded images with the labels and create the initial yaml configuration file to be used in the YOLO training process.
* **Export dataset**: exports the loaded images and their labels as a YOLO
dataset, with a configurable (and optionally shuffled) train/validation
split.

* **Synthesize dataset**: Create a synthetic dataset from the drawn labels randomizing the object position in different images (useful when a low number of images is available).
* **Synthesize**: creates a synthetic dataset by pasting the annotated
crops at random, non-overlapping positions onto uniform backgrounds
(useful when only a few labeled images are available).
93 changes: 77 additions & 16 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
# Works with both uv and conda:
# - If a project-local uv virtualenv (./.venv) exists, its tools are
# used automatically (no activation needed).
# - Otherwise the active environment is used (conda, system, ...).
dot_venv_bin := justfile_directory() / ".venv/bin"
venv_bin := justfile_directory() / "venv/bin"
src_dir := justfile_directory() / "src"

export PATH := if path_exists(dot_venv_bin) == "true" {
dot_venv_bin + ":" + env("PATH")
} else if path_exists(venv_bin) == "true" {
venv_bin + ":" + env("PATH")
} else {
env("PATH")
}

# Import the package from ./src regardless of the editable install.
# This keeps the checks working even when the .pth file of the install
# is unreadable to Python, which happens on macOS when a synced folder
# (iCloud Desktop/Documents) sets the "hidden" flag on it: Python >=
# 3.11 silently skips hidden .pth files.
export PYTHONPATH := if env_var_or_default("PYTHONPATH", "") == "" {
src_dir
} else {
src_dir + ":" + env_var_or_default("PYTHONPATH", "")
}

# List all commands.
default:
@just --list
Expand All @@ -8,33 +35,67 @@ docs:
make -C docs html
echo Docs are in $PWD/docs/build/html/index.html

# Do a dev install.
# Do a dev install (uv venv, conda or plain pip - autodetected).
dev:
pip install -e '.[dev]'
#!/usr/bin/env bash
set -euo pipefail
# An existing project venv wins over the active environment, and
# ./.venv wins over ./venv (same order as the PATH setting above).
target=""
for candidate in .venv venv; do
if [ -d "$candidate" ]; then target="$candidate"; break; fi
done
if [ -n "$target" ] && command -v uv >/dev/null 2>&1; then
echo "Installing into ./$target with uv"
uv pip install --python "$target/bin/python" -e '.[dev]'
elif [ -n "${CONDA_PREFIX:-}" ]; then
echo "Installing into conda env '${CONDA_DEFAULT_ENV:-}' with pip"
pip install -e '.[dev]'
elif command -v uv >/dev/null 2>&1; then
echo "Creating ./.venv with uv"
uv venv
uv pip install -e '.[dev]'
else
pip install -e '.[dev]'
fi
# On macOS the .pth file of the editable install can carry the
# "hidden" flag, which makes Python >= 3.11 skip it, so that the
# package fails to import (including from the label_tool command).
# Some setups keep re-applying the flag to dot-directories such as
# ./.venv; a venv named ./venv avoids it. The recipes above do not
# depend on the .pth anyway: they import the package from ./src.
if [ "$(uname)" = "Darwin" ] && [ -n "$target" ]; then
chflags nohidden "$target"/lib/python*/site-packages/*.pth 2>/dev/null || true
fi

# Run code checks.
check:
#!/usr/bin/env bash

# bash 3.2 (the macOS default) does not run the ERR trap when a
# subshell fails, so failures are collected explicitly: without this,
# `just check` reported success even when a step failed.
error=0
trap error=1 ERR
failed=()

echo
(set -x; ruff check . )
run() {
echo
( set -x; "$@" ) || { error=1; failed+=("$1"); }
}

echo
( set -x; ruff format --check . )
run ruff check .
run ruff format --check .
run mypy .
run pytest --cov=src --cov-report term-missing
run make -C docs doctest

echo
( set -x; mypy . )

echo
( set -x; pytest --cov=src --cov-report term-missing )

echo
( set -x; make -C docs doctest )

test $error = 0
if [ $error -ne 0 ]; then
echo "FAILED: ${failed[*]}"
else
echo "All checks passed."
fi
exit $error

# Auto-fix code issues.
fix:
Expand Down
17 changes: 15 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,19 @@ line-length = 79

[tool.ruff.lint]
select = ["ALL"]
ignore = ["ANN401", "COM812", "ISC001", "FBT001", "FBT002", "PLR0913", "G004"]
# CPY001: the licence lives in LICENSE, not in a header of every file.
# PLR0917: counterpart of PLR0913 for positional arguments.
ignore = [
"ANN401",
"COM812",
"CPY001",
"ISC001",
"FBT001",
"FBT002",
"PLR0913",
"PLR0917",
"G004",
]

[tool.ruff.lint.pydocstyle]
convention = "google"
Expand Down Expand Up @@ -109,7 +121,8 @@ check_untyped_defs = true
disallow_untyped_decorators = true
warn_unreachable = true
disallow_any_generics = true
exclude = 'docs/build/html/_static'
# Directories starting with a dot (such as .venv) are skipped already.
exclude = ['docs/build/html/_static', '^venv/']

[[tool.mypy.overrides]]
module = [
Expand Down
21 changes: 15 additions & 6 deletions src/dynsight/_internal/analysis/spatial_average.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ def spatialaverage(

Raises:
ValueError:
If the input descriptor array does not have 2 or 3 dimensions,
an error is raised.
If the input descriptor array does not have 2 or 3 dimensions, or
if its number of frames does not match the (sliced) trajectory.

Example:

Expand Down Expand Up @@ -184,15 +184,24 @@ def spatialaverage(
msg = "descriptor_array must have ndim == 2 or ndim == 3."
raise ValueError(msg)

frame_indices = list(
range(*trajslice.indices(universe.trajectory.n_frames))
)
if descriptor_array.shape[1] != len(frame_indices):
msg = (
f"descriptor_array covers {descriptor_array.shape[1]} frames, but "
f"the trajectory (after slicing) has {len(frame_indices)}. "
"Descriptors such as LENS and timeSOAP are defined on pairs of "
"frames and are one frame shorter than the trajectory they come "
"from: slice the Trj to match before averaging."
)
raise ValueError(msg)

pool = Pool(
processes=n_jobs,
initializer=initworker,
initargs=(shared_array, shape, dtype),
)

frame_indices = list(
range(*trajslice.indices(universe.trajectory.n_frames))
)
args = [
(universe, selection, r_cut, traj_frame, i, is_vector)
for i, traj_frame in enumerate(frame_indices)
Expand Down
Loading
Loading