Skip to content

Commit 9917fda

Browse files
niksirbisfmiglochhhpre-commit-ci[bot]
authored
Document napari plugin (#393)
* started user guide about napari plugin * added optional dependencies for napari-video and pyvideoreader * WIP expanding on the napari plugin user guide * add section on loading poses datasets * set maximum card columns to 2 in the user guide page * corrected grammar and spelling mistakes in the plugin guide * removed automatic setting of playback fps to tracking fps * update to v3 of pyvista/setup-headless-display-action * Apply suggestions from Sofia's code review Co-authored-by: sfmig <33267254+sfmig@users.noreply.github.com> * renamed napari guide and added clarifications * fps spinbox: make float, default 1, add tooltip * made image annotations more prominent * Apply suggestions from CH's code review Co-authored-by: Chang Huan Lo <changhuan.lo@ucl.ac.uk> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * applied more suggestions from CH's review * documented known issues * renamed napari_plugin.md to gui.md * add link to github issues tagged with GUI + bug * Use movement-github link from myst-url-scheme Co-authored-by: Chang Huan Lo <changhuan.lo@ucl.ac.uk> --------- Co-authored-by: sfmig <33267254+sfmig@users.noreply.github.com> Co-authored-by: Chang Huan Lo <changhuan.lo@ucl.ac.uk> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 105899a commit 9917fda

File tree

8 files changed

+204
-23
lines changed

8 files changed

+204
-23
lines changed

.github/workflows/test_and_deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141

4242
steps:
4343
# these libraries enable testing on Qt on linux
44-
- uses: pyvista/setup-headless-display-action@v2
44+
- uses: pyvista/setup-headless-display-action@v3
4545
with:
4646
qt: true
4747
- name: Cache Test Data
346 KB
Loading

docs/source/community/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ develop a new feature, or improve the documentation.
55
To help you get started, we have prepared a statement on the project's [mission and scope](target-mission),
66
a [roadmap](target-roadmaps) outlining our current priorities, and a detailed [contributing guide](target-contributing).
77

8+
(target-get-in-touch)=
89
```{include} ../snippets/get-in-touch.md
910
```
1011

docs/source/user_guide/gui.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
(target-gui)=
2+
# Graphical User Interface
3+
4+
The `movement` graphical user interface (GUI), powered by our custom plugin for
5+
[napari](napari:), makes it easy to view and explore `movement`
6+
motion tracks. Currently, you can use it to
7+
visualise 2D [poses datasets](target-poses-and-bboxes-dataset)
8+
as points overlaid on video frames.
9+
10+
:::{warning}
11+
The GUI is still in early stages of development but we are working on ironing
12+
out the [kinks](movement-github:issues?q=sort%3Aupdated-desc+is%3Aissue+state%3Aopen+label%3AGUI+label%3Abug).
13+
Please [get in touch](target-get-in-touch)
14+
if you find any bugs or have suggestions for improvements!
15+
:::
16+
17+
The `napari` plugin is shipped with the `movement` package starting from
18+
version `0.1.0`. To use it, you need to
19+
[install the package](target-installation) via a method that
20+
includes the `napari` dependency.
21+
22+
23+
## Launch the GUI
24+
25+
To launch the `movement` GUI, type the following command in your terminal:
26+
27+
```sh
28+
movement launch
29+
```
30+
31+
This is equivalent to running `napari -w movement` and will open the `napari`
32+
window with the `movement` widget docked on the
33+
right-hand side, as in the [screenshot](target-widget-screenshot) below.
34+
35+
In `napari`, data is typically loaded into [layers](napari:guides/layers.html),
36+
which can be reordered and toggled for visibility in the layers list panel.
37+
For example, keypoint data can be added as a
38+
[points layer](napari:howtos/layers/points.html),
39+
while image stacks (including videos) can be added as
40+
[image layers](napari:howtos/layers/image.html).
41+
Below, we'll explain how to do this.
42+
43+
## Load a background layer
44+
45+
Though this is not strictly necessary, it is usually informative to
46+
view the keypoints overlaid on a background that provides
47+
some spatial context. You can either [load the video](target-load-video)
48+
corresponding to the poses dataset, or a [single image](target-load-frame),
49+
e.g., a still frame derived from that video.
50+
You can do this by dragging and dropping the corresponding file onto the
51+
`napari` window or by using the `File > Open File(s)` menu option.
52+
Please read the following sections for detailed information
53+
and some important considerations.
54+
55+
(target-load-video)=
56+
### Load a video
57+
58+
When trying to load a video file into `napari`, you will be prompted
59+
via a pop-up dialog to select the reader.
60+
Choose the `video` reader—corresponding to the
61+
[`napari-video`](https://github.com/janclemenslab/napari-video)
62+
plugin—and click `OK`. You can optionally select to remember this reader
63+
for all files with the same extension.
64+
65+
`napari-video` will load the video as an image stack with a slider
66+
at the bottom that you can use to navigate through frames.
67+
You may also use the left and right arrow keys to navigate
68+
frame-by-frame.
69+
70+
Clicking on the play button will start the video playback at a default
71+
rate of 10 frames per second. You can adjust that by right-clicking on the
72+
play button or by opening the `napari > Preferences` menu
73+
(`File > Preferences` on Windows) and changing
74+
the `Playback frames per second` setting.
75+
76+
(target-video-playback-limitations)=
77+
:::{admonition} Video playback limitations
78+
:class: warning
79+
80+
- The video playback may freeze or stutter if you click on the slider to jump
81+
to a specific frame. We recommended pausing the playback before such jumps.
82+
- `napari-video` may struggle to play videos at a high frame rate, depending
83+
on your hardware, the video resolution and codec. If you experience
84+
performance issues, such as the video freezing or skipping frames,
85+
try reducing the playback frames per second or fall back to
86+
using a [single image](target-load-frame) as a background.
87+
:::
88+
89+
90+
(target-load-frame)=
91+
### Load an image
92+
93+
This usually means using a still frame extracted from the video, but in theory
94+
you could use any image that's in the same coordinate system as the
95+
tracking data. For example, you could use a schematic diagram of the arena,
96+
as long as it has the same width and height as the video and is
97+
properly aligned with the tracking data.
98+
99+
::: {dropdown} Extracting a still frame from a video
100+
:color: info
101+
:icon: info
102+
103+
You can use the command line tool [`ffmpeg`](https://www.ffmpeg.org/)
104+
to extract a still frame from a video.
105+
106+
To extract the first frame of a video:
107+
108+
```sh
109+
ffmpeg -i video.mp4 -frames:v 1 first-frame.png
110+
```
111+
112+
To extract a frame at a specific time stamp (e.g. at 2 seconds):
113+
114+
```sh
115+
ffmpeg -i video.mp4 -ss 00:00:02 -frames:v 1 frame-2sec.png
116+
```
117+
:::
118+
119+
Dragging and dropping the image file onto the `napari` window
120+
(or opening it via the `File` menu) will load the image
121+
as a single 2D frame without a slider.
122+
123+
## Load the poses dataset
124+
125+
Now you are ready to load some pose tracks over your chosen background layer.
126+
127+
On the right-hand side of the window you should see
128+
an expanded `Load poses` menu. To load pose data in napari:
129+
1. Select the `source software` from the dropdown menu.
130+
2. Set the `fps` (frames per second) of the video the pose data refers to. Note this will only affect the units of the time variable shown when hovering over a keypoint. If the `fps` is not known, you can set it to 1, which will effectively make the time variable equal to the frame number.
131+
3. Select the file containing the predicted poses. The path can be directly pasted or you can use the file browser button.
132+
4. Click `Load`.
133+
134+
The data should be loaded into the viewer as a
135+
[points layer](napari:howtos/layers/points.html).
136+
By default, it is added at the top of the layer list.
137+
138+
::: {note}
139+
See [supported formats](target-supported-formats) for more information on
140+
the expected software and file formats.
141+
:::
142+
143+
144+
You will see a view similar to the one below:
145+
146+
(target-widget-screenshot)=
147+
148+
![napari widget with poses dataset loaded](../_static/napari_plugin_with_poses_as_points.png)
149+
150+
The keypoints are represented as points, colour-coded by
151+
keypoint ID for single-individual datasets, or by individual ID for
152+
multi-individual datasets. These IDs can be also displayed as text
153+
next to the points by enabling the `display text` option from the
154+
layer controls panel.
155+
156+
Hovering with your mouse over a point
157+
(with the points layer selected) will
158+
bring up a tooltip containing the names of the individual and keypoint,
159+
the point-wise confidence score (provided by the source software),
160+
and the time in seconds (calculated based on the frame number and
161+
the `fps` value you provided).
162+
163+
Using the slider at the bottom of the window, you can move through
164+
the frames of the dataset, and the points and video will update
165+
in sync.
166+
167+
::: {admonition} Stay tuned
168+
Though the display style of the points layer is currently fixed, we are
169+
working on adding more customisation options in future releases, such as
170+
enabling you to change the point size, colour, or shape.
171+
172+
We are also working on enabling the visualisation of
173+
[bounding boxes datasets](target-poses-and-bboxes-dataset) in the plugin.
174+
:::

docs/source/user_guide/index.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Before you dive deeper, we highly recommend reading about the structure
88
and usage of [movement datasets](movement_dataset.md), which are a central
99
concept in the package.
1010

11-
::::{grid} 1 2 2 3
11+
::::{grid} 1 1 2 2
1212
:gutter: 3
1313

1414
:::{grid-item-card} {fas}`wrench;sd-text-primary` Installation
@@ -32,6 +32,13 @@ Load and save tracking data.
3232
Learn about our data structures.
3333
:::
3434

35+
:::{grid-item-card} {fas}`line-chart;sd-text-primary` Graphical User Interface
36+
:link: gui
37+
:link-type: doc
38+
39+
Use our `napari` plugin to view and explore your data interactively.
40+
:::
41+
3542
::::
3643

3744

@@ -42,4 +49,5 @@ Learn about our data structures.
4249
installation
4350
input_output
4451
movement_dataset
52+
gui
4553
```

movement/napari/_loader_widgets.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
from napari.viewer import Viewer
99
from qtpy.QtWidgets import (
1010
QComboBox,
11+
QDoubleSpinBox,
1112
QFileDialog,
1213
QFormLayout,
1314
QHBoxLayout,
1415
QLineEdit,
1516
QPushButton,
16-
QSpinBox,
1717
QWidget,
1818
)
1919

@@ -56,11 +56,20 @@ def _create_source_software_widget(self):
5656

5757
def _create_fps_widget(self):
5858
"""Create a spinbox for selecting the frames per second (fps)."""
59-
self.fps_spinbox = QSpinBox()
59+
self.fps_spinbox = QDoubleSpinBox()
6060
self.fps_spinbox.setObjectName("fps_spinbox")
61-
self.fps_spinbox.setMinimum(1)
62-
self.fps_spinbox.setMaximum(1000)
63-
self.fps_spinbox.setValue(30)
61+
self.fps_spinbox.setMinimum(0.1)
62+
self.fps_spinbox.setMaximum(1000.0)
63+
self.fps_spinbox.setValue(1.0)
64+
self.fps_spinbox.setDecimals(2)
65+
# How much we increment/decrement when the user clicks the arrows
66+
self.fps_spinbox.setSingleStep(1)
67+
# Add a tooltip
68+
self.fps_spinbox.setToolTip(
69+
"Set the frames per second of the tracking data.\n"
70+
"This just affects the displayed time when hovering over a point\n"
71+
"(it doesn't set the playback speed)."
72+
)
6473
self.layout().addRow("fps:", self.fps_spinbox)
6574

6675
def _create_file_path_widget(self):
@@ -124,9 +133,6 @@ def _on_load_clicked(self):
124133
self.file_name = Path(file_path).name
125134
self._add_points_layer()
126135

127-
self._set_playback_fps(fps)
128-
logger.debug(f"Set napari playback speed to {fps} fps.")
129-
130136
def _add_points_layer(self):
131137
"""Add the predicted poses to the viewer as a Points layer."""
132138
# Style properties for the napari Points layer
@@ -144,12 +150,6 @@ def _add_points_layer(self):
144150
self.viewer.add_points(self.data[:, 1:], **points_style.as_kwargs())
145151
logger.info("Added poses dataset as a napari Points layer.")
146152

147-
@staticmethod
148-
def _set_playback_fps(fps: int):
149-
"""Set the playback speed for the napari viewer."""
150-
settings = get_settings()
151-
settings.application.playback_fps = fps
152-
153153
@staticmethod
154154
def _enable_layer_tooltips():
155155
"""Toggle on tooltip visibility for napari layers.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ entry-points."napari.manifest".movement = "movement.napari:napari.yaml"
4848
[project.optional-dependencies]
4949
napari = [
5050
"napari[all]>=0.5.0",
51-
"brainglobe-utils[qt]>=0.6" # needed for collapsible widgets
51+
"brainglobe-utils[qt]>=0.6", # needed for collapsible widgets
52+
"napari-video",
53+
"pyvideoreader>=0.5.3", # since switching to depend on openCV-headless
5254
]
5355
dev = [
5456
"pytest",

tests/test_unit/test_napari_plugin/test_poses_loader_widget.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pytest
99
from napari.settings import get_settings
1010
from pytest import DATA_PATHS
11-
from qtpy.QtWidgets import QComboBox, QLineEdit, QPushButton, QSpinBox
11+
from qtpy.QtWidgets import QComboBox, QDoubleSpinBox, QLineEdit, QPushButton
1212

1313
from movement.napari._loader_widgets import PosesLoader
1414

@@ -25,7 +25,7 @@ def test_poses_loader_widget_instantiation(make_napari_viewer_proxy):
2525
# Check that the expected widgets are present in the layout
2626
expected_widgets = [
2727
(QComboBox, "source_software_combo"),
28-
(QSpinBox, "fps_spinbox"),
28+
(QDoubleSpinBox, "fps_spinbox"),
2929
(QLineEdit, "file_path_edit"),
3030
(QPushButton, "load_button"),
3131
(QPushButton, "browse_button"),
@@ -160,14 +160,10 @@ def test_on_load_clicked_with_valid_file_path(
160160
"Converted poses dataset to a napari Tracks array.",
161161
"Tracks array shape: (2170, 4)",
162162
"Added poses dataset as a napari Points layer.",
163-
"Set napari playback speed to 60 fps.",
164163
}
165164
log_messages = {record.getMessage() for record in caplog.records}
166165
assert expected_log_messages <= log_messages
167166

168167
# Check that a Points layer was added to the viewer
169168
points_layer = poses_loader_widget.viewer.layers[0]
170169
assert points_layer.name == f"poses: {file_path.name}"
171-
172-
# Check that the playback fps was set correctly
173-
assert get_settings().application.playback_fps == 60

0 commit comments

Comments
 (0)