Skip to content

Commit 1f81888

Browse files
committed
Fix RectangularROIModel docstring
1 parent 42ed8b3 commit 1f81888

File tree

3 files changed

+65
-16
lines changed

3 files changed

+65
-16
lines changed

src/ndv/models/_roi_model.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
class RectangularROIModel(NDVModel):
1414
"""Representation of an axis-aligned rectangular Region of Interest (ROI).
1515
16-
Parameters
16+
Attributes
1717
----------
1818
visible : bool
1919
Whether to display this roi.
20-
bounding_box: tuple[tuple[float, float], tuple[float, float]]
20+
bounding_box : tuple[tuple[float, float], tuple[float, float]]
2121
The minimum (2D) point and the maximum (2D) point contained within the
2222
region. Using these two points, an axis-aligned bounding box can be
2323
constructed.

src/ndv/views/_vispy/_array_canvas.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,14 @@ def __init__(self, parent: Any) -> None:
141141
self._handles.order = 100
142142
self._handles.interactive = True
143143

144-
self._tform = self._rect.transforms.get_transform("canvas", "scene")
145-
146144
self.set_fill(_cmap.Color("transparent"))
147145
self.set_border(_cmap.Color("yellow"))
148146
self.set_handles(_cmap.Color("white"))
149147
self.set_visible(False)
150148

149+
def _tform(self) -> scene.transforms.BaseTransform:
150+
return self._rect.transforms.get_transform("canvas", "scene")
151+
151152
def can_select(self) -> bool:
152153
return True
153154

@@ -206,7 +207,7 @@ def set_bounding_box(
206207
def on_mouse_move(self, event: MouseMoveEvent) -> bool:
207208
# Convert canvas -> world
208209
canvas_pos = (event.x, event.y)
209-
world_pos = self._tform.map(canvas_pos)[:2]
210+
world_pos = self._tform().map(canvas_pos)[:2]
210211
# moving a handle
211212
if self._move_mode == ROIMoveMode.HANDLE:
212213
# The anchor is set to the opposite handle, which never moves.
@@ -229,7 +230,7 @@ def on_mouse_press(self, event: MousePressEvent) -> bool:
229230
self.set_selected(True)
230231
# Convert canvas -> world
231232
canvas_pos = (event.x, event.y)
232-
world_pos = self._tform.map(canvas_pos)[:2]
233+
world_pos = self._tform().map(canvas_pos)[:2]
233234
drag_idx = self._handle_under(world_pos)
234235
# If a marker is pressed
235236
if drag_idx is not None:
@@ -247,7 +248,7 @@ def on_mouse_release(self, event: MouseReleaseEvent) -> bool:
247248

248249
def get_cursor(self, mme: MouseMoveEvent) -> CursorType | None:
249250
canvas_pos = (mme.x, mme.y)
250-
pos = self._tform.map(canvas_pos)[:2]
251+
pos = self._tform().map(canvas_pos)[:2]
251252
if self._handle_under(pos) is not None:
252253
center = self._rect.center
253254
if pos[0] < center[0] and pos[1] < center[1]:

tests/test_controller.py

+57-9
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
import numpy as np
1010
import pytest
1111

12-
from ndv._types import MouseButton, MouseMoveEvent, MousePressEvent, MouseReleaseEvent
12+
from ndv._types import (
13+
CursorType,
14+
MouseButton,
15+
MouseMoveEvent,
16+
MousePressEvent,
17+
MouseReleaseEvent,
18+
)
1319
from ndv.controllers import ArrayViewer
1420
from ndv.controllers._channel_controller import ChannelController
1521
from ndv.models._array_display_model import ArrayDisplayModel, ChannelMode
@@ -309,18 +315,21 @@ def test_roi_interaction() -> None:
309315
return
310316

311317
ctrl = ArrayViewer()
318+
roi = RectangularROIModel(bounding_box=((0, 0), (3000, 3000)))
319+
ctrl.roi = roi
320+
roi_view = ctrl._roi_view
321+
assert roi_view is not None
312322

313-
canvas_roi_start = (0, 0)
323+
# Note that these positions are far apart to satisfy sufficient distance
324+
# in world space
325+
ctrl._canvas.set_range()
326+
canvas_roi_start = (200, 200)
314327
world_roi_start = tuple(ctrl._canvas.canvas_to_world(canvas_roi_start)[:2])
315-
canvas_new_start = (-100, -100)
328+
canvas_new_start = (100, 100)
316329
world_new_start = tuple(ctrl._canvas.canvas_to_world(canvas_new_start)[:2])
317-
canvas_roi_end = (100, 100)
330+
canvas_roi_end = (300, 300)
318331
world_roi_end = tuple(ctrl._canvas.canvas_to_world(canvas_roi_end)[:2])
319-
320-
roi = RectangularROIModel(bounding_box=(world_roi_start, world_roi_end))
321-
ctrl.roi = roi
322-
roi_view = ctrl._roi_view
323-
assert roi_view is not None
332+
roi.bounding_box = (world_roi_start, world_roi_end)
324333

325334
# Note - avoid diving into rendering logic here - just identify view
326335
with patch.object(ctrl._canvas, "elements_at", return_value=[ctrl._roi_view]):
@@ -339,3 +348,42 @@ def test_roi_interaction() -> None:
339348
canvas_new_start[0], canvas_new_start[1], MouseButton.LEFT
340349
)
341350
ctrl._canvas.on_mouse_release(mre)
351+
352+
# Test translation
353+
roi.bounding_box = (world_roi_start, world_roi_end)
354+
mpe = MousePressEvent(
355+
(canvas_roi_start[0] + canvas_roi_end[0] / 2),
356+
(canvas_roi_start[1] + canvas_roi_end[1] / 2),
357+
MouseButton.LEFT,
358+
)
359+
ctrl._canvas.on_mouse_press(mpe)
360+
assert roi_view.selected()
361+
mme = MouseMoveEvent(
362+
(canvas_roi_start[0] + canvas_new_start[0] / 2),
363+
(canvas_roi_start[1] + canvas_new_start[1] / 2),
364+
MouseButton.LEFT,
365+
)
366+
ctrl._canvas.on_mouse_move(mme)
367+
assert roi.bounding_box[0] == pytest.approx(world_new_start, 1e-6)
368+
assert roi.bounding_box[1] == pytest.approx(world_roi_start, 1e-6)
369+
mre = MouseReleaseEvent(
370+
(canvas_roi_start[0] + canvas_new_start[0] / 2),
371+
(canvas_roi_start[1] + canvas_new_start[1] / 2),
372+
MouseButton.LEFT,
373+
)
374+
ctrl._canvas.on_mouse_release(mre)
375+
376+
# Test cursors
377+
roi.bounding_box = (world_roi_start, world_roi_end)
378+
# Top-Left corner
379+
mme = MouseMoveEvent(canvas_roi_start[0], canvas_roi_start[1])
380+
assert roi_view.get_cursor(mme) == CursorType.FDIAG_ARROW
381+
# Top-Right corner
382+
mme = MouseMoveEvent(canvas_roi_start[0], canvas_roi_end[1])
383+
assert roi_view.get_cursor(mme) == CursorType.BDIAG_ARROW
384+
# Middle
385+
mme = MouseMoveEvent(
386+
(canvas_roi_start[0] + canvas_roi_end[0]) / 2,
387+
(canvas_roi_start[1] + canvas_roi_end[1]) / 2,
388+
)
389+
assert roi_view.get_cursor(mme) == CursorType.ALL_ARROW

0 commit comments

Comments
 (0)