Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions src/ndv/controllers/_array_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ def __init__(
# where None is the default channel
self._lut_controllers: dict[ChannelKey, ChannelController] = {}

# Thread-safe initialization: queue GUI-sensitive operations to main thread
init_future = _app.ndv_app().call_in_main_thread(self._init_gui_components)
# Block until GUI components are initialized
init_future.result()

if self._data_model.data_wrapper is not None:
# Queue view synchronization to main thread as well
# since it involves GUI operations
sync_future = _app.ndv_app().call_in_main_thread(
self._fully_synchronize_view
)
sync_future.result()

def _init_gui_components(self) -> None:
"""Initialize GUI components that must be created on the main thread."""
# get and create the front-end and canvas classes
frontend_cls = _app.get_array_view_class()
canvas_cls = _app.get_array_canvas_class()
Expand All @@ -123,9 +138,6 @@ def __init__(
self._canvas.mouseMoved.connect(self._on_canvas_mouse_moved)
self._canvas.mouseLeft.connect(self._on_canvas_mouse_left)

if self._data_model.data_wrapper is not None:
self._fully_synchronize_view()

# -------------- public attributes and methods -------------------------

def widget(self) -> Any:
Expand Down Expand Up @@ -210,15 +222,22 @@ def roi(self, roi_model: RectangularROIModel | None) -> None:

def show(self) -> None:
"""Show the viewer."""
self._view.set_visible(True)
# Queue GUI operation to main thread to ensure thread safety
show_future = _app.ndv_app().call_in_main_thread(self._view.set_visible, True)
# Block until operation completes to maintain synchronous behavior
show_future.result()

def hide(self) -> None:
"""Hide the viewer."""
self._view.set_visible(False)
# Queue GUI operation to main thread to ensure thread safety
hide_future = _app.ndv_app().call_in_main_thread(self._view.set_visible, False)
hide_future.result()

def close(self) -> None:
"""Close the viewer."""
self._view.set_visible(False)
# Queue GUI operation to main thread to ensure thread safety
close_future = _app.ndv_app().call_in_main_thread(self._view.set_visible, False)
close_future.result()

def clone(self) -> ArrayViewer:
"""Return a new ArrayViewer instance with the same data and display model.
Expand Down
Loading