Skip to content

Commit c3102bf

Browse files
committed
fix(napari): show playback FPS label on meta widget (Issue #423)
1 parent 8d49d6a commit c3102bf

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

movement/napari/loader_widgets.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
QFileDialog,
1717
QFormLayout,
1818
QHBoxLayout,
19+
QLabel,
1920
QLineEdit,
2021
QPushButton,
2122
QWidget,
@@ -440,3 +441,34 @@ def _enable_layer_tooltips():
440441
"""
441442
settings = get_settings()
442443
settings.appearance.layer_tooltip_visibility = True
444+
445+
446+
class PlaybackFPSWidget(QWidget):
447+
"""Widget that displays the current napari playback FPS.
448+
449+
Shows a label that reads the playback FPS directly from napari's
450+
application settings (``get_settings().application.playback_fps``) and
451+
updates whenever the user changes the playback speed in napari.
452+
453+
This is intentionally read-only and does *not* measure FPS in real time
454+
to avoid flickering or warnings related to private napari attributes.
455+
"""
456+
457+
def __init__(self, napari_viewer: Viewer, parent=None):
458+
"""Initialize the playback FPS widget."""
459+
super().__init__(parent=parent)
460+
self.viewer = napari_viewer
461+
self.setLayout(QFormLayout())
462+
463+
playback_fps = get_settings().application.playback_fps
464+
self.playback_fps_label = QLabel(f"Playback FPS: {playback_fps}")
465+
self.playback_fps_label.setObjectName("playback_fps_label")
466+
self.layout().addRow(self.playback_fps_label)
467+
468+
get_settings().application.events.playback_fps.connect(
469+
self._on_fps_changed
470+
)
471+
472+
def _on_fps_changed(self, event):
473+
"""Update the label when the playback fps changes."""
474+
self.playback_fps_label.setText(f"Playback FPS: {event.value}")

movement/napari/meta_widget.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from napari.viewer import Viewer
44
from qt_niu.collapsible_widget import CollapsibleWidgetContainer
55

6-
from movement.napari.loader_widgets import DataLoader
6+
from movement.napari.loader_widgets import DataLoader, PlaybackFPSWidget
77

88

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

27+
# Add the playback fps widget
28+
self.add_widget(
29+
PlaybackFPSWidget(napari_viewer, parent=self),
30+
collapsible=False,
31+
widget_title="Playback FPS",
32+
)
33+
2734
self.loader = self.collapsible_widgets[0]
2835
self.loader.expand() # expand the loader widget by default

tests/test_unit/test_napari_plugin/test_data_loader_widget.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
from qtpy.QtWidgets import (
2727
QComboBox,
2828
QDoubleSpinBox,
29+
QLabel,
2930
QLineEdit,
3031
QPushButton,
3132
)
3233

3334
from movement.napari.loader_widgets import (
3435
SUPPORTED_BBOXES_FILES,
3536
DataLoader,
37+
PlaybackFPSWidget,
3638
)
3739

3840
pytestmark = pytest.mark.filterwarnings(
@@ -916,3 +918,39 @@ def test_add_points_and_tracks_layer_style(
916918
np.testing.assert_allclose(
917919
bboxes_layer_colormap_sorted, text_colormap_sorted, atol=1e-7
918920
)
921+
922+
923+
# ------------------- tests for PlaybackFPSWidget ----------------------------#
924+
925+
926+
def test_playback_fps_widget_instantiation(make_napari_viewer_proxy):
927+
"""Test that the PlaybackFPSWidget is properly instantiated.
928+
929+
The label should display the current playback FPS from napari's
930+
application settings (``get_settings().application.playback_fps``)
931+
and the label widget should be findable by name.
932+
"""
933+
viewer = make_napari_viewer_proxy()
934+
widget = PlaybackFPSWidget(viewer)
935+
936+
label = widget.findChild(QLabel, "playback_fps_label")
937+
assert label is not None, "playback_fps_label widget not found."
938+
expected_fps = get_settings().application.playback_fps
939+
assert label.text() == f"Playback FPS: {expected_fps}"
940+
941+
942+
def test_playback_fps_label_updates_on_fps_change(make_napari_viewer_proxy):
943+
"""Test that the label updates when the playback fps setting changes.
944+
945+
After the settings event fires, the label text should reflect the
946+
new value.
947+
"""
948+
viewer = make_napari_viewer_proxy()
949+
widget = PlaybackFPSWidget(viewer)
950+
951+
new_fps = 42
952+
get_settings().application.playback_fps = new_fps
953+
954+
label = widget.findChild(QLabel, "playback_fps_label")
955+
assert label is not None, "playback_fps_label widget not found."
956+
assert label.text() == f"Playback FPS: {new_fps}"

0 commit comments

Comments
 (0)