Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion docs/source/community/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ If you are not familiar with `git`, we recommend reading up on [this guide](http
### Forking the repository

1. Fork the [repository](movement-github:) on GitHub.
You can read more about [forking in the GitHub docs](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
You can read more about [forking in the GitHub docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo).

2. Clone your fork to your local machine and navigate to the repository folder:

Expand Down
32 changes: 32 additions & 0 deletions movement/napari/loader_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
QFileDialog,
QFormLayout,
QHBoxLayout,
QLabel,
QLineEdit,
QPushButton,
QWidget,
Expand Down Expand Up @@ -440,3 +441,34 @@ def _enable_layer_tooltips():
"""
settings = get_settings()
settings.appearance.layer_tooltip_visibility = True


class PlaybackFPSWidget(QWidget):
"""Widget that displays the current napari playback FPS.

Shows a label that reads the playback FPS directly from napari's
application settings (``get_settings().application.playback_fps``) and
updates whenever the user changes the playback speed in napari.

This is intentionally read-only and does *not* measure FPS in real time
to avoid flickering or warnings related to private napari attributes.
"""

def __init__(self, napari_viewer: Viewer, parent=None):
"""Initialize the playback FPS widget."""
super().__init__(parent=parent)
self.viewer = napari_viewer
self.setLayout(QFormLayout())

playback_fps = get_settings().application.playback_fps
self.playback_fps_label = QLabel(f"Playback FPS: {playback_fps}")
self.playback_fps_label.setObjectName("playback_fps_label")
self.layout().addRow(self.playback_fps_label)

get_settings().application.events.playback_fps.connect(
self._on_fps_changed
)

def _on_fps_changed(self, event):
"""Update the label when the playback fps changes."""
self.playback_fps_label.setText(f"Playback FPS: {event.value}")
9 changes: 8 additions & 1 deletion movement/napari/meta_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from napari.viewer import Viewer
from qt_niu.collapsible_widget import CollapsibleWidgetContainer

from movement.napari.loader_widgets import DataLoader
from movement.napari.loader_widgets import DataLoader, PlaybackFPSWidget


class MovementMetaWidget(CollapsibleWidgetContainer):
Expand All @@ -24,5 +24,12 @@ def __init__(self, napari_viewer: Viewer, parent=None):
widget_title="Load tracked data",
)

# Add the playback fps widget
self.add_widget(
PlaybackFPSWidget(napari_viewer, parent=self),
collapsible=False,
widget_title="Playback FPS",
)

self.loader = self.collapsible_widgets[0]
self.loader.expand() # expand the loader widget by default
41 changes: 41 additions & 0 deletions tests/test_unit/test_napari_plugin/test_data_loader_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
from qtpy.QtWidgets import (
QComboBox,
QDoubleSpinBox,
QLabel,
QLineEdit,
QPushButton,
)

from movement.napari.loader_widgets import (
SUPPORTED_BBOXES_FILES,
DataLoader,
PlaybackFPSWidget,
)

pytestmark = pytest.mark.filterwarnings(
Expand Down Expand Up @@ -916,3 +918,42 @@ def test_add_points_and_tracks_layer_style(
np.testing.assert_allclose(
bboxes_layer_colormap_sorted, text_colormap_sorted, atol=1e-7
)


# ------------------- tests for PlaybackFPSWidget ----------------------------#


def test_playback_fps_widget_instantiation(make_napari_viewer_proxy):
"""Test that the PlaybackFPSWidget is properly instantiated.

The label should display the current playback FPS from napari's
application settings (``get_settings().application.playback_fps``)
and the label widget should be findable by name.
"""
viewer = make_napari_viewer_proxy()
widget = PlaybackFPSWidget(viewer)

label = widget.findChild(QLabel, "playback_fps_label")
assert label is not None, "playback_fps_label widget not found."
expected_fps = get_settings().application.playback_fps
assert label.text() == f"Playback FPS: {expected_fps}"


def test_playback_fps_label_updates_on_fps_change(
make_napari_viewer_proxy, monkeypatch
):
"""Test that the label updates when the playback fps setting changes.

After the settings event fires, the label text should reflect the
new value.
"""
viewer = make_napari_viewer_proxy()
widget = PlaybackFPSWidget(viewer)

new_fps = 42
# Use monkeypatch to ensure the global setting is restored after the test
monkeypatch.setattr(get_settings().application, "playback_fps", new_fps)

label = widget.findChild(QLabel, "playback_fps_label")
assert label is not None, "playback_fps_label widget not found."
assert label.text() == f"Playback FPS: {new_fps}"
Comment on lines +953 to +959
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test mutates the global napari setting get_settings().application.playback_fps but never restores the previous value. That can make other tests order-dependent (they may see a non-default playback_fps). Save the original playback_fps at the start of the test and restore it in a finally block (or use a fixture) after the assertion.

Suggested change
new_fps = 42
get_settings().application.playback_fps = new_fps
label = widget.findChild(QLabel, "playback_fps_label")
assert label is not None, "playback_fps_label widget not found."
assert label.text() == f"Playback FPS: {new_fps}"
original_fps = get_settings().application.playback_fps
new_fps = 42
try:
get_settings().application.playback_fps = new_fps
label = widget.findChild(QLabel, "playback_fps_label")
assert label is not None, "playback_fps_label widget not found."
assert label.text() == f"Playback FPS: {new_fps}"
finally:
get_settings().application.playback_fps = original_fps

Copilot uses AI. Check for mistakes.
Loading