From 480cbddc49cfee7d553b2fd3ce6d37bcca41f6d1 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Wed, 10 Jul 2024 13:34:18 -0400 Subject: [PATCH] add tests --- .../points_plans/_points_plan_widget.py | 26 ++--- .../points_plans/_well_graphics_view.py | 32 +++--- tests/useq_widgets/test_useq_points_plans.py | 99 ++++++++++++++++++- 3 files changed, 127 insertions(+), 30 deletions(-) diff --git a/src/pymmcore_widgets/useq_widgets/points_plans/_points_plan_widget.py b/src/pymmcore_widgets/useq_widgets/points_plans/_points_plan_widget.py index ec2f21114..0261d6e58 100644 --- a/src/pymmcore_widgets/useq_widgets/points_plans/_points_plan_widget.py +++ b/src/pymmcore_widgets/useq_widgets/points_plans/_points_plan_widget.py @@ -21,37 +21,37 @@ def __init__( ) -> None: super().__init__(parent=parent) - self.selector = RelativePointPlanSelector() + self._selector = RelativePointPlanSelector() # graphics scene to draw the well and the fovs - self.well_view = WellView() + self._well_view = WellView() # main layout = QHBoxLayout(self) - layout.addWidget(self.selector, 1) - layout.addWidget(self.well_view, 2) + layout.addWidget(self._selector, 1) + layout.addWidget(self._well_view, 2) # connect - self.selector.valueChanged.connect(self._on_selector_value_changed) - self.well_view.maxPointsDetected.connect(self._on_view_max_points_detected) - self.well_view.positionClicked.connect(self._on_view_position_clicked) + self._selector.valueChanged.connect(self._on_selector_value_changed) + self._well_view.maxPointsDetected.connect(self._on_view_max_points_detected) + self._well_view.positionClicked.connect(self._on_view_position_clicked) if plan is not None: self.setValue(plan) def value(self) -> useq.RelativeMultiPointPlan: - return self.selector.value() + return self._selector.value() def setValue(self, plan: useq.RelativeMultiPointPlan) -> None: - self.selector.setValue(plan) + self._selector.setValue(plan) def _on_selector_value_changed(self, value: useq.RelativeMultiPointPlan) -> None: - self.well_view.setPointsPlan(value) + self._well_view.setPointsPlan(value) self.valueChanged.emit(value) def _on_view_max_points_detected(self, value: int) -> None: - self.selector.random_points_wdg.num_points.setValue(value) + self._selector.random_points_wdg.num_points.setValue(value) def _on_view_position_clicked(self, position: useq.RelativePosition) -> None: - if self.selector.active_plan_type is useq.RandomPoints: + if self._selector.active_plan_type is useq.RandomPoints: pos_no_name = position.model_copy(update={"name": ""}) - self.selector.random_points_wdg.start_at = pos_no_name + self._selector.random_points_wdg.start_at = pos_no_name diff --git a/src/pymmcore_widgets/useq_widgets/points_plans/_well_graphics_view.py b/src/pymmcore_widgets/useq_widgets/points_plans/_well_graphics_view.py index 105b51409..297cc2f44 100644 --- a/src/pymmcore_widgets/useq_widgets/points_plans/_well_graphics_view.py +++ b/src/pymmcore_widgets/useq_widgets/points_plans/_well_graphics_view.py @@ -12,7 +12,7 @@ from pymmcore_widgets._util import ResizingGraphicsView if TYPE_CHECKING: - from PyQt6.QtGui import QMouseEvent + from qtpy.QtGui import QMouseEvent DATA_POSITION = 1 @@ -50,6 +50,11 @@ def __init__(self, parent: QWidget | None = None) -> None: def sizeHint(self) -> QSize: return QSize(500, 500) + def setWellSize(self, width_mm: float | None, height_mm: float | None) -> None: + """Set the well size width and height in mm.""" + self._well_width_um = (width_mm * 1000) if width_mm else None + self._well_height_um = (height_mm * 1000) if height_mm else None + def setPointsPlan(self, plan: useq.RelativeMultiPointPlan) -> None: """Set the plan to use to draw the FOVs.""" self._fov_width_um = plan.fov_width @@ -63,21 +68,6 @@ def setPointsPlan(self, plan: useq.RelativeMultiPointPlan) -> None: # DRAW FOVS self._draw_fovs(plan) - def mousePressEvent(self, event: QMouseEvent | None) -> None: - if event is None: - return - scene_pos = self.mapToScene(event.pos()) - items = self.scene().items(scene_pos) - for item in items: - if pos := item.data(DATA_POSITION): - self.positionClicked.emit(pos) - break - - def setWellSize(self, width_mm: float | None, height_mm: float | None) -> None: - """Set the well size width and height in mm.""" - self._well_width_um = (width_mm * 1000) if width_mm else None - self._well_height_um = (height_mm * 1000) if height_mm else None - def _draw_outline(self) -> None: """Draw the outline of the well area.""" if self._outline_item: @@ -191,3 +181,13 @@ def _scaled_pen_size(self) -> int: def _resize_to_fit(self) -> None: self.setSceneRect(self._scene.itemsBoundingRect()) self.resizeEvent(None) + + def mousePressEvent(self, event: QMouseEvent | None) -> None: + if event is not None: + scene_pos = self.mapToScene(event.pos()) + print(scene_pos) + items = self.scene().items(scene_pos) + for item in items: + if pos := item.data(DATA_POSITION): + self.positionClicked.emit(pos) + break diff --git a/tests/useq_widgets/test_useq_points_plans.py b/tests/useq_widgets/test_useq_points_plans.py index 01efc0715..8e2be7baa 100644 --- a/tests/useq_widgets/test_useq_points_plans.py +++ b/tests/useq_widgets/test_useq_points_plans.py @@ -2,7 +2,11 @@ from typing import TYPE_CHECKING -from useq import GridRowsColumns, OrderMode, RandomPoints, RelativePosition +import pytest +import useq +from qtpy.QtCore import Qt +from qtpy.QtGui import QMouseEvent +from useq import GridRowsColumns, OrderMode, RandomPoints, RelativePosition, Shape from pymmcore_widgets.useq_widgets import points_plans as pp @@ -31,6 +35,8 @@ relative_to="top_left", ) +RELATIVE_POSITION = RelativePosition() + def test_random_points_widget(qtbot: QtBot) -> None: wdg = pp.RandomPointWidget() @@ -86,6 +92,10 @@ def test_point_plan_selector(qtbot: QtBot) -> None: assert wdg.value() == RANDOM_POINTS assert wdg.random_radio_btn.isChecked() + wdg.setValue(RELATIVE_POSITION) + assert wdg.value() == RELATIVE_POSITION + assert wdg.single_radio_btn.isChecked() + wdg.setValue(GRID_ROWS_COLS) assert wdg.value() == GRID_ROWS_COLS assert wdg.grid_radio_btn.isChecked() @@ -99,3 +109,90 @@ def test_point_plan_selector(qtbot: QtBot) -> None: "fov_height": GRID_ROWS_COLS.fov_height, } ) + + +def test_points_plan_widget(qtbot: QtBot) -> None: + """PointsPlanWidget is a RelativePointPlanSelector combined with a graphics view.""" + wdg = pp.PointsPlanWidget() + wdg.show() + qtbot.addWidget(wdg) + + for plan in (RANDOM_POINTS, RELATIVE_POSITION, GRID_ROWS_COLS): + with qtbot.waitSignal(wdg.valueChanged): + wdg.setValue(plan) + assert wdg.value() == plan + + +@pytest.mark.parametrize( + "plan", + [ + RELATIVE_POSITION, + GRID_ROWS_COLS, + RANDOM_POINTS, + RANDOM_POINTS.replace(shape=Shape.ELLIPSE), + RANDOM_POINTS.replace(fov_width=None), + RANDOM_POINTS.replace(fov_width=None, fov_height=None), + ], +) +def test_points_plan_variants(plan: useq.RelativeMultiPointPlan, qtbot: QtBot) -> None: + """Test PointsPlanWidget with different plan types.""" + wdg = pp.PointsPlanWidget(plan) + wdg.show() + qtbot.addWidget(wdg) + # make sure the view can also render without a well size + wdg._well_view.setWellSize(None, None) + wdg._well_view.setPointsPlan(plan) + assert wdg.value() == plan + + +def test_clicking_point_changes_first_position(qtbot: QtBot) -> None: + plan = RandomPoints( + num_points=20, + random_seed=0, + fov_width=500, + fov_height=500, + max_width=1000, + max_height=1000, + ) + wdg = pp.PointsPlanWidget(plan) + wdg.show() + qtbot.addWidget(wdg) + + assert isinstance(wdg.value().start_at, int) + + # clicking on a point should change the start_at position + event = QMouseEvent( + QMouseEvent.Type.MouseButtonPress, + wdg._well_view.mapFromScene(0, 0).toPointF(), + Qt.MouseButton.LeftButton, + Qt.MouseButton.LeftButton, + Qt.KeyboardModifier.NoModifier, + ) + wdg._well_view.mousePressEvent(event) + + new_val = wdg.value() + assert isinstance(new_val.start_at, useq.RelativePosition) + rounded = round(new_val.start_at) + # feel free to relax this if it ever fails tests + assert rounded.x == 122 + assert rounded.y == -50 + + +def test_max_points_detected(qtbot: QtBot) -> None: + plan = RandomPoints( + num_points=20, + random_seed=0, + fov_width=500, + fov_height=500, + max_width=1000, + max_height=1000, + allow_overlap=False, + ) + wdg = pp.PointsPlanWidget(plan) + wdg.show() + qtbot.addWidget(wdg) + + with qtbot.waitSignal(wdg._well_view.maxPointsDetected): + wdg._selector.random_points_wdg.num_points.setValue(100) + + assert wdg.value().num_points < 60