9
9
from ndv .controllers ._channel_controller import ChannelController
10
10
from ndv .models import ArrayDisplayModel , ChannelMode , DataWrapper , LUTModel
11
11
from ndv .models ._data_display_model import DataResponse , _ArrayDataDisplayModel
12
+ from ndv .models ._roi_model import RectangularROIModel
13
+ from ndv .models ._viewer_model import ArrayViewerModel , InteractionMode
12
14
from ndv .views import _app
15
+ from ndv .views .bases ._graphics ._canvas_elements import RectangularROI
13
16
14
17
if TYPE_CHECKING :
15
18
from concurrent .futures import Future
@@ -68,6 +71,9 @@ def __init__(
68
71
self ._data_model = _ArrayDataDisplayModel (
69
72
data_wrapper = data , display = display_model or ArrayDisplayModel (** kwargs )
70
73
)
74
+ self ._viewer_model = ArrayViewerModel ()
75
+ self ._viewer_model .events .interaction_mode .connect (self ._on_interaction_mode_changed )
76
+ self ._roi_model : RectangularROIModel | None = None
71
77
72
78
app = _app .gui_frontend ()
73
79
@@ -87,10 +93,12 @@ def __init__(
87
93
# get and create the front-end and canvas classes
88
94
frontend_cls = _app .get_array_view_class ()
89
95
canvas_cls = _app .get_array_canvas_class ()
90
- self ._canvas = canvas_cls ()
96
+ self ._canvas = canvas_cls (self . _viewer_model )
91
97
92
98
self ._histogram : HistogramCanvas | None = None
93
- self ._view = frontend_cls (self ._canvas .frontend_widget (), self ._data_model )
99
+ self ._view = frontend_cls (self ._canvas .frontend_widget (), self ._data_model , self ._viewer_model )
100
+
101
+ self ._roi_view : RectangularROI | None = None
94
102
95
103
self ._set_model_connected (self ._data_model .display )
96
104
self ._canvas .set_ndim (self .display_model .n_visible_axes )
@@ -161,6 +169,19 @@ def data(self, data: Any) -> None:
161
169
else :
162
170
self ._data_model .data_wrapper = DataWrapper .create (data )
163
171
self ._fully_synchronize_view ()
172
+
173
+ @property
174
+ def roi (self ) -> RectangularROIModel | None :
175
+ return self ._roi_model
176
+
177
+ @roi .setter
178
+ def roi (self , roi_model : RectangularROIModel | None ) -> None :
179
+ if self ._roi_model is not None :
180
+ self ._set_roi_model_connected (self ._roi_model , False )
181
+ self ._roi_model = roi_model
182
+ if self ._roi_model is not None :
183
+ self ._set_roi_model_connected (self ._roi_model )
184
+ self ._fully_synchronize_view ()
164
185
165
186
def show (self ) -> None :
166
187
"""Show the viewer."""
@@ -238,6 +259,22 @@ def _set_model_connected(
238
259
]:
239
260
getattr (obj , _connect )(callback )
240
261
262
+ def _set_roi_model_connected (
263
+ self , model : RectangularROIModel , connect : bool = True
264
+ ) -> None :
265
+ """Connect or disconnect the model to/from the viewer.
266
+
267
+ We do this in a single method so that we are sure to connect and disconnect
268
+ the same events in the same order. (but it's kinda ugly)
269
+ """
270
+ _connect = "connect" if connect else "disconnect"
271
+
272
+ for obj , callback in [
273
+ (model .events .bounding_box , self ._on_roi_model_bounding_box_changed ),
274
+ (model .events .visible , self ._on_roi_model_visible_changed ),
275
+ ]:
276
+ getattr (obj , _connect )(callback )
277
+
241
278
# ------------------ Model callbacks ------------------
242
279
243
280
def _fully_synchronize_view (self ) -> None :
@@ -261,6 +298,9 @@ def _fully_synchronize_view(self) -> None:
261
298
for lut_ctr in self ._lut_controllers .values ():
262
299
lut_ctr ._update_view_from_model ()
263
300
self ._update_hist_domain_for_dtype ()
301
+ if self .roi is not None :
302
+ self ._on_roi_model_bounding_box_changed (self .roi .bounding_box )
303
+ self ._on_roi_model_visible_changed (self .roi .visible )
264
304
265
305
def _on_model_visible_axes_changed (self ) -> None :
266
306
self ._view .set_visible_axes (self ._data_model .normed_visible_axes )
@@ -287,6 +327,31 @@ def _on_model_channel_mode_changed(self, mode: ChannelMode) -> None:
287
327
# redraw
288
328
self ._clear_canvas ()
289
329
self ._request_data ()
330
+
331
+ def _on_roi_model_bounding_box_changed (self , bb : tuple [tuple [float , float ], tuple [float , float ]]) -> None :
332
+ if self ._roi_view is None :
333
+ self ._roi_view = self ._canvas .add_bounding_box ()
334
+ # HACK
335
+ self ._roi_view .set_visible (True )
336
+ self ._roi_view .boundingBoxChanged .connect (self ._on_roi_view_bounding_box_changed )
337
+ self ._roi_view .set_bounding_box (* bb )
338
+
339
+ def _on_roi_model_visible_changed (self , visible : bool ) -> None :
340
+ if self ._roi_view is None :
341
+ self ._roi_view = self ._canvas .add_bounding_box ()
342
+ # HACK
343
+ self ._roi_view .set_visible (True )
344
+ self ._roi_view .boundingBoxChanged .connect (self ._on_roi_view_bounding_box_changed )
345
+ self ._roi_view .set_visible (visible )
346
+
347
+ def _on_interaction_mode_changed (self , mode : InteractionMode ) -> None :
348
+ # TODO: Unify with _on_roi_model_bounding_box_changed
349
+ if mode == InteractionMode .CREATE_ROI :
350
+ if self ._roi_view :
351
+ self ._roi_view .remove ()
352
+ self ._roi_view = self ._canvas .add_bounding_box ()
353
+ # HACK
354
+ self ._roi_view .boundingBoxChanged .connect (self ._on_roi_view_bounding_box_changed )
290
355
291
356
def _clear_canvas (self ) -> None :
292
357
for lut_ctrl in self ._lut_controllers .values ():
@@ -308,6 +373,10 @@ def _on_view_visible_axes_changed(self) -> None:
308
373
def _on_view_reset_zoom_clicked (self ) -> None :
309
374
"""Reset the zoom level of the canvas."""
310
375
self ._canvas .set_range ()
376
+
377
+ def _on_roi_view_bounding_box_changed (self , bb : tuple [tuple [float , float ], tuple [float , float ]]) -> None :
378
+ if self ._roi_model :
379
+ self ._roi_model .bounding_box = bb
311
380
312
381
def _on_canvas_mouse_moved (self , event : MouseMoveEvent ) -> None :
313
382
"""Respond to a mouse move event in the view."""
0 commit comments