Skip to content

Commit 7d3ac57

Browse files
committed
Fix RectangularROIModel docstring
1 parent 42ed8b3 commit 7d3ac57

File tree

3 files changed

+69
-16
lines changed

3 files changed

+69
-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

+61-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,25 @@ def test_roi_interaction() -> None:
309315
return
310316

311317
ctrl = ArrayViewer()
318+
roi = RectangularROIModel()
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+
# FIXME: We need a large world space on the canvas, but
324+
# VispyArrayCanvas.set_range is not implemented yet. This workaround
325+
# sets the range to the extent of the data i.e. the extent of the ROI
326+
roi.bounding_box = ((0, 0), (500, 500))
327+
ctrl._canvas.set_range()
328+
# Note that these positions are far apart to satisfy sufficient distance
329+
# in world space
330+
canvas_roi_start = (200, 200)
314331
world_roi_start = tuple(ctrl._canvas.canvas_to_world(canvas_roi_start)[:2])
315-
canvas_new_start = (-100, -100)
332+
canvas_new_start = (100, 100)
316333
world_new_start = tuple(ctrl._canvas.canvas_to_world(canvas_new_start)[:2])
317-
canvas_roi_end = (100, 100)
334+
canvas_roi_end = (300, 300)
318335
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
336+
roi.bounding_box = (world_roi_start, world_roi_end)
324337

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

0 commit comments

Comments
 (0)