From e6de2dde5bd22ee4d69669a52bfa9cff97886123 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Tue, 5 Nov 2024 21:14:08 -0500 Subject: [PATCH] misc wip --- mvc.ipynb | 13 +- mvc.py | 3 +- src/ndv/controller.py | 47 +++- src/ndv/v2view_jupyter.py | 98 ++++++++- src/ndv/v2view_qt.py | 6 +- src/ndv/viewer_v2.py | 450 +++++++++++++++++++------------------- x.py | 15 -- 7 files changed, 368 insertions(+), 264 deletions(-) delete mode 100644 x.py diff --git a/mvc.ipynb b/mvc.ipynb index 869a868..404e39c 100644 --- a/mvc.ipynb +++ b/mvc.ipynb @@ -8,7 +8,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9d6de969d9034cc8aca2b4347b2ebad6", + "model_id": "ddb975849c044d2aa8c7d5b7b55f6bde", "version_major": 2, "version_minor": 0 }, @@ -22,7 +22,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1aaf69f5944841a09e09911cb8d08ff4", + "model_id": "e64758a0a08f493f95bb52446c7853be", "version_major": 2, "version_minor": 0 }, @@ -37,15 +37,14 @@ "source": [ "from IPython.display import display\n", "\n", + "from ndv.controller import ViewerController\n", "from ndv.data import cells3d\n", - "from ndv.v2ctl import ViewerController\n", - "from ndv.v2view_jupyter import JupyterViewerView\n", "\n", - "viewer = ViewerController(JupyterViewerView())\n", + "viewer = ViewerController()\n", "model = viewer.model\n", "viewer.data = cells3d()\n", "\n", - "display(viewer.view.layout)" + "viewer.view.show()" ] }, { @@ -90,7 +89,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.6" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/mvc.py b/mvc.py index 35c8633..bcb5c5c 100644 --- a/mvc.py +++ b/mvc.py @@ -2,11 +2,10 @@ from ndv.controller import ViewerController from ndv.data import cells3d -from ndv.v2view_qt import QViewerView app = QApplication([]) -viewer = ViewerController(QViewerView()) # ultimately, this will be the public api +viewer = ViewerController() # ultimately, this will be the public api viewer.data = cells3d() viewer.view.show() app.exec() diff --git a/src/ndv/controller.py b/src/ndv/controller.py index 3a57511..bc44fff 100644 --- a/src/ndv/controller.py +++ b/src/ndv/controller.py @@ -1,8 +1,9 @@ +import sys from collections.abc import Container, Hashable, Mapping, MutableMapping, Sequence from typing import Any, Protocol import cmap -from psygnal import SignalInstance +from psygnal import Signal, SignalInstance from pydantic import Field from .models._array_display_model import ArrayDisplayModel, AxisKey @@ -96,10 +97,10 @@ def current_data(self) -> Any: class PLutView(Protocol): - visibleChanged: SignalInstance - autoscaleChanged: SignalInstance - cmapChanged: SignalInstance - climsChanged: SignalInstance + visibleChanged: Signal + autoscaleChanged: Signal + cmapChanged: Signal + climsChanged: Signal def setName(self, name: str) -> None: ... def setAutoScale(self, auto: bool) -> None: ... @@ -123,12 +124,15 @@ def hide_sliders( ) -> None: ... def add_lut_view(self) -> PLutView: ... + def show(self) -> None: ... class ViewerController: """The controller mostly manages the connection between the model and the view.""" - def __init__(self, view: PView) -> None: + def __init__(self, view: PView | None = None) -> None: + if view is None: + view = _pick_view_backend() self.view = view self._dd_model = DataDisplayModel() # rename me self._set_model_connected(self._dd_model.display) @@ -212,7 +216,6 @@ def _on_slider_value_changed(self) -> None: def _update_canvas(self) -> None: if not self._dd_model.data: return - data = self._dd_model.current_data() # TODO: make asynchronous if None in self._handles: self._handles[None].data = data @@ -245,7 +248,7 @@ def add_lut_view(self, key: int | None) -> PLutView: model_lut.events.cmap.connect(lut.setColormap) model_lut.events.clims.connect(lut.setClims) model_lut.events.autoscale.connect(lut.setAutoScale) - model_lut.events.visible.connect(lut.setVisible) + model_lut.events.visible.connect(lut.setLutVisible) return lut def _on_lut_visible_changed(self, visible: bool) -> None: @@ -258,3 +261,31 @@ def _on_cmap_changed(self, cmap: cmap.Colormap) -> None: def _on_clims_changed(self, clims: tuple[float, float]) -> None: self._handles[None].clim = clims + + +def _pick_view_backend() -> PView: + if _is_running_in_notebook(): + from .v2view_jupyter import JupyterViewerView + + return JupyterViewerView() + elif _is_running_in_qapp(): + from .v2view_qt import QViewerView + + return QViewerView() + + raise RuntimeError("Could not determine the appropriate viewer backend") + + +def _is_running_in_notebook() -> bool: + if IPython := sys.modules.get("IPython"): + if shell := IPython.get_ipython(): + return bool(shell.__class__.__name__ == "ZMQInteractiveShell") + return False + + +def _is_running_in_qapp() -> bool: + for mod_name in ("PyQt5", "PySide2", "PySide6", "PyQt6"): + if mod := sys.modules.get(f"{mod_name}.QtWidgets"): + if qapp := getattr(mod, "QApplication", None): + return qapp.instance() is not None + return False diff --git a/src/ndv/v2view_jupyter.py b/src/ndv/v2view_jupyter.py index 6935caf..387feb0 100644 --- a/src/ndv/v2view_jupyter.py +++ b/src/ndv/v2view_jupyter.py @@ -1,12 +1,86 @@ -from collections.abc import Container, Hashable, Mapping, Sequence -from typing import Any +from __future__ import annotations +from typing import TYPE_CHECKING, Any + +import cmap import ipywidgets as widgets from psygnal import Signal -from .models._array_display_model import AxisKey from .viewer._backends import get_canvas_class -from .viewer._backends._protocols import PImageHandle + +if TYPE_CHECKING: + from collections.abc import Container, Hashable, Mapping, Sequence + + from .controller import PLutView + from .models._array_display_model import AxisKey + from .viewer._backends._protocols import PImageHandle + + +class JupyterLutView: + visibleChanged = Signal(bool) + autoscaleChanged = Signal(bool) + cmapChanged = Signal(cmap.Colormap) + climsChanged = Signal(tuple) + + def __init__(self) -> None: + self._visible = widgets.Checkbox(value=True) + self._visible.observe(self._on_visible_changed, names="value") + + self._cmap = widgets.Dropdown( + options=["gray", "green", "magenta"], value="gray" + ) + self._cmap.observe(self._on_cmap_changed, names="value") + + self._clims = widgets.FloatRangeSlider( + value=[0, 2**16], + min=0, + max=2**16, + step=1, + orientation="horizontal", + readout=True, + readout_format=".0f", + ) + self._clims.observe(self._on_clims_changed, names="value") + + self._auto_clim = widgets.ToggleButton( + value=True, + description="Auto", + button_style="", # 'success', 'info', 'warning', 'danger' or '' + tooltip="Auto scale", + icon="check", + ) + self._auto_clim.observe(self._on_autoscale_changed, names="value") + + self.layout = widgets.HBox( + [self._visible, self._cmap, self._clims, self._auto_clim] + ) + + def _on_clims_changed(self, change: dict[str, Any]) -> None: + self.climsChanged(self._clims.value) + + def _on_visible_changed(self, change: dict[str, Any]) -> None: + self.visibleChanged(self._visible.value) + + def _on_cmap_changed(self, change: dict[str, Any]) -> None: + self.cmapChanged(cmap.Colormap(self._cmap.value)) + + def _on_autoscale_changed(self, change: dict[str, Any]) -> None: + self.autoscaleChanged(self._auto_clim.value) + + def setName(self, name: str) -> None: + self._visible.description = name + + def setAutoScale(self, auto: bool) -> None: + self._auto_clim.value = auto + + def setColormap(self, cmap: cmap.Colormap) -> None: + self._cmap.value = cmap.name + + def setClims(self, clims: tuple[float, float]) -> None: + self._clims.value = clims + + def setLutVisible(self, visible: bool) -> None: + self._visible.value = visible class JupyterViewerView: @@ -77,3 +151,19 @@ def set_current_index(self, value: Mapping[AxisKey, int | slice]) -> None: if isinstance(val, slice): raise NotImplementedError("Slices are not supported yet") self._sliders[axis].value = val + + def add_lut_view(self) -> PLutView: + """Add a LUT view to the viewer.""" + wdg = JupyterLutView() + self.layout.children = (*self.layout.children, wdg.layout) + return wdg + + def show(self) -> None: + """Show the viewer.""" + from IPython.display import display + + display(self.layout) # type: ignore [no-untyped-call] + + def refresh(self) -> None: + """Refresh the viewer.""" + self._canvas.refresh() diff --git a/src/ndv/v2view_qt.py b/src/ndv/v2view_qt.py index 61431a0..780857e 100644 --- a/src/ndv/v2view_qt.py +++ b/src/ndv/v2view_qt.py @@ -35,7 +35,7 @@ def showPopup(self) -> None: popup.move(popup.x(), popup.y() - self.height() - popup.height()) -class LUTWidget(QWidget): +class QLUTWidget(QWidget): visibleChanged = Signal(bool) autoscaleChanged = Signal(bool) cmapChanged = Signal(cmap.Colormap) @@ -157,8 +157,8 @@ def __init__(self, parent: QWidget | None = None): layout.addWidget(self._dims_sliders) layout.addWidget(self._luts) - def add_lut_view(self) -> LUTWidget: - wdg = LUTWidget(self) + def add_lut_view(self) -> QLUTWidget: + wdg = QLUTWidget(self) self._luts.addWidget(wdg) return wdg diff --git a/src/ndv/viewer_v2.py b/src/ndv/viewer_v2.py index 8f1c4ee..10ff4c2 100644 --- a/src/ndv/viewer_v2.py +++ b/src/ndv/viewer_v2.py @@ -1,225 +1,225 @@ -from collections.abc import Hashable, Iterable, Mapping, Sequence -from typing import TYPE_CHECKING, Any, cast - -from qtpy.QtCore import Qt, Signal -from qtpy.QtWidgets import QFormLayout, QSlider, QVBoxLayout, QWidget -from superqt import QLabeledSlider -from superqt.utils import signals_blocked - -from .models._array_display_model import ArrayDisplayModel, AxisKey -from .viewer._backends import get_canvas_class -from .viewer._data_wrapper import DataWrapper - -if TYPE_CHECKING: - from collections.abc import MutableMapping - - from .viewer._backends import PCanvas - - -class DimsSliders(QWidget): - valueChanged = Signal() - - def __init__(self, parent: QWidget | None = None): - super().__init__() - self._sliders: MutableMapping[Hashable, QSlider] = {} - - layout = QFormLayout(self) - layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) - - def update_sliders(self, coords: Mapping[Hashable, Sequence]) -> None: - # TODO: consider what the axis key is here. - # it's possible this should be canonicalized before calling this method - # because the axis key is used to index the sliders internally - for axis, _coords in coords.items(): - self._sliders[axis] = sld = QLabeledSlider(Qt.Orientation.Horizontal) - sld.valueChanged.connect(self.valueChanged) - if isinstance(_coords, range): - sld.setRange(_coords.start, _coords.stop - 1) - sld.setSingleStep(_coords.step) - else: - raise NotImplementedError("Only range is supported for now") - cast("QFormLayout", self.layout()).addRow(str(axis), sld) - - def hide_dimensions( - self, dims: Iterable[Hashable], *, show_remainder: bool = True - ) -> None: - """Hide sliders corresponding to dimensions in `dims`.""" - # TODO: dims here much be the same as those used to index the sliders - # in update_sliders. - _dims = set(dims) - layout = cast("QFormLayout", self.layout()) - for ax, slider in self._sliders.items(): - if ax in _dims: - layout.setRowVisible(slider, False) - elif show_remainder: - layout.setRowVisible(slider, True) - - def value(self) -> Mapping[AxisKey, int | slice]: - """Return the current value of the sliders.""" - return {axis: slider.value() for axis, slider in self._sliders.items()} - - def setValue(self, value: Mapping[AxisKey, int | slice]) -> None: - """Set the current value of the sliders.""" - for axis, val in value.items(): - if isinstance(val, slice): - raise NotImplementedError("Slices are not supported yet") - self._sliders[axis].setValue(val) - - -class Viewer(QWidget): - _data_wrapper: DataWrapper | None - _display_model: ArrayDisplayModel - - def __init__( - self, data: Any = None, display_model: ArrayDisplayModel | None = None - ): - super().__init__() - self.model = display_model or ArrayDisplayModel() - - self._dims_sliders = DimsSliders() - self._dims_sliders.valueChanged.connect(self._on_sliders_value_changed) - self._canvas: PCanvas = get_canvas_class()() - - layout = QVBoxLayout(self) - layout.addWidget(self._canvas.qwidget()) - layout.addWidget(self._dims_sliders) - - self.data = data - - @property - def data(self) -> Any: - """Return data being displayed.""" - if self._data_wrapper is None: - return None - return self._data_wrapper.data - - @data.setter - def data(self, data: Any) -> None: - """Return the data to be displayed.""" - if data is None: - self._data_wrapper = None - return - else: - self._data_wrapper = DataWrapper.create(data) - - # the data is the thing that tells us how many sliders to show - # when the data changes we get dims and coords from the data - # TODO: we need some sort of signal from the DataWrapper to trigger update - # short of full replacement of the data - dims = self._data_wrapper.dims - coords = { - self._canonicalize_axis_key(ax, dims): c - for ax, c in self._data_wrapper.coords.items() - } - self._dims_sliders.update_sliders(coords) - self._update_visible_sliders() - - @property - def model(self) -> ArrayDisplayModel: - """Return the display model for the viewer.""" - return self._display_model - - @model.setter - def model(self, display_model: ArrayDisplayModel) -> None: - """Set the display model for the viewer.""" - display_model = ArrayDisplayModel.model_validate(display_model) - previous_model: ArrayDisplayModel | None = getattr(self, "_display_model", None) - if previous_model is not None: - self._set_model_connected(previous_model, False) - - self._display_model = display_model - self._set_model_connected(display_model) - - def _set_model_connected( - self, model: ArrayDisplayModel, connect: bool = True - ) -> None: - """Connect or disconnect the model to/from the viewer. - - We do this in a single method so that we are sure to connect and disconnect - the same events in the same order. - """ - _connect = "connect" if connect else "disconnect" - - for obj, callback in [ - (model.events.visible_axes, self._on_visible_axes_changed), - # the current_index attribute itself is immutable - (model.current_index.value_changed, self._on_current_index_changed), - (model.events.channel_axis, self._on_channel_axis_changed), - # TODO: lut values themselves are mutable evented objects... - # so we need to connect to their events as well - (model.luts.value_changed, self._on_luts_changed), - ]: - getattr(obj, _connect)(callback) - - def _on_visible_axes_changed( - self, value: tuple[AxisKey, AxisKey, AxisKey] | tuple[AxisKey, AxisKey] - ) -> None: - self._update_visible_sliders() - self._canvas.set_ndim(self.model.n_visible_axes) - - def _update_visible_sliders(self) -> None: - """Hide sliders corresponding to "visible" axes.""" - if self._data_wrapper is None: - return - dims = self._data_wrapper.dims - hide = {self._canonicalize_axis_key(ax, dims) for ax in self.model.visible_axes} - self._dims_sliders.hide_dimensions(hide, show_remainder=True) - - def _on_current_index_changed(self) -> None: - value = self.model.current_index - with signals_blocked(self._dims_sliders): - self._dims_sliders.setValue(value) - self._update_canvas() - - def _on_sliders_value_changed(self) -> None: - value = self._dims_sliders.value() - self.model.current_index.update(value) - - def _canonicalize_axis_key(self, axis: AxisKey, dims: Sequence[Hashable]) -> int: - """Return positive index for AxisKey (which can be +/- int or label).""" - # TODO: improve performance by indexing ahead of time - if isinstance(axis, int): - ndims = len(dims) - ax = axis if axis >= 0 else len(dims) + axis - if ax >= ndims: - raise IndexError( - f"Axis index {axis} out of bounds for data with {ndims} dimensions" - ) - return ax - try: - return dims.index(axis) - except ValueError as e: - raise IndexError(f"Axis label {axis} not found in data dimensions") from e - - def _current_index_request(self) -> Mapping[int, int | slice]: - # Generate cannocalized index request - if self._data_wrapper is None: - return {} - - dims = self._data_wrapper.dims - idx_request = { - self._canonicalize_axis_key(ax, dims): v - for ax, v in self.model.current_index.items() - } - for ax in self.model.visible_axes: - ax_ = self._canonicalize_axis_key(ax, dims) - if not isinstance(idx_request.get(ax_), slice): - idx_request[ax_] = slice(None) - return idx_request - - def _update_canvas(self) -> None: - # get the data from the data wrapper - if self._data_wrapper is None: - return - idx_request = self._current_index_request() - data = self._data_wrapper.isel(idx_request) - if hdl := getattr(self, "_handle", None): - hdl.remove() - self._handle = self._canvas.add_image(data) - self._canvas.set_range() - - def _on_channel_axis_changed(self, value: AxisKey) -> None: - print("Channel axis changed:", value) - - def _on_luts_changed(self) -> None: - print("LUTs changed", self.model.luts) +# from collections.abc import Hashable, Iterable, Mapping, Sequence +# from typing import TYPE_CHECKING, Any, cast + +# from qtpy.QtCore import Qt, Signal +# from qtpy.QtWidgets import QFormLayout, QSlider, QVBoxLayout, QWidget +# from superqt import QLabeledSlider +# from superqt.utils import signals_blocked + +# from .models._array_display_model import ArrayDisplayModel, AxisKey +# from .viewer._backends import get_canvas_class +# from .viewer._data_wrapper import DataWrapper + +# if TYPE_CHECKING: +# from collections.abc import MutableMapping + +# from .viewer._backends import PCanvas + + +# class DimsSliders(QWidget): +# valueChanged = Signal() + +# def __init__(self, parent: QWidget | None = None): +# super().__init__() +# self._sliders: MutableMapping[Hashable, QSlider] = {} + +# layout = QFormLayout(self) +# layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) + +# def update_sliders(self, coords: Mapping[Hashable, Sequence]) -> None: +# # TODO: consider what the axis key is here. +# # it's possible this should be canonicalized before calling this method +# # because the axis key is used to index the sliders internally +# for axis, _coords in coords.items(): +# self._sliders[axis] = sld = QLabeledSlider(Qt.Orientation.Horizontal) +# sld.valueChanged.connect(self.valueChanged) +# if isinstance(_coords, range): +# sld.setRange(_coords.start, _coords.stop - 1) +# sld.setSingleStep(_coords.step) +# else: +# raise NotImplementedError("Only range is supported for now") +# cast("QFormLayout", self.layout()).addRow(str(axis), sld) + +# def hide_dimensions( +# self, dims: Iterable[Hashable], *, show_remainder: bool = True +# ) -> None: +# """Hide sliders corresponding to dimensions in `dims`.""" +# # TODO: dims here much be the same as those used to index the sliders +# # in update_sliders. +# _dims = set(dims) +# layout = cast("QFormLayout", self.layout()) +# for ax, slider in self._sliders.items(): +# if ax in _dims: +# layout.setRowVisible(slider, False) +# elif show_remainder: +# layout.setRowVisible(slider, True) + +# def value(self) -> Mapping[AxisKey, int | slice]: +# """Return the current value of the sliders.""" +# return {axis: slider.value() for axis, slider in self._sliders.items()} + +# def setValue(self, value: Mapping[AxisKey, int | slice]) -> None: +# """Set the current value of the sliders.""" +# for axis, val in value.items(): +# if isinstance(val, slice): +# raise NotImplementedError("Slices are not supported yet") +# self._sliders[axis].setValue(val) + + +# class Viewer(QWidget): +# _data_wrapper: DataWrapper | None +# _display_model: ArrayDisplayModel + +# def __init__( +# self, data: Any = None, display_model: ArrayDisplayModel | None = None +# ): +# super().__init__() +# self.model = display_model or ArrayDisplayModel() + +# self._dims_sliders = DimsSliders() +# self._dims_sliders.valueChanged.connect(self._on_sliders_value_changed) +# self._canvas: PCanvas = get_canvas_class()() + +# layout = QVBoxLayout(self) +# layout.addWidget(self._canvas.qwidget()) +# layout.addWidget(self._dims_sliders) + +# self.data = data + +# @property +# def data(self) -> Any: +# """Return data being displayed.""" +# if self._data_wrapper is None: +# return None +# return self._data_wrapper.data + +# @data.setter +# def data(self, data: Any) -> None: +# """Return the data to be displayed.""" +# if data is None: +# self._data_wrapper = None +# return +# else: +# self._data_wrapper = DataWrapper.create(data) + +# # the data is the thing that tells us how many sliders to show +# # when the data changes we get dims and coords from the data +# # TODO: we need some sort of signal from the DataWrapper to trigger update +# # short of full replacement of the data +# dims = self._data_wrapper.dims +# coords = { +# self._canonicalize_axis_key(ax, dims): c +# for ax, c in self._data_wrapper.coords.items() +# } +# self._dims_sliders.update_sliders(coords) +# self._update_visible_sliders() + +# @property +# def model(self) -> ArrayDisplayModel: +# """Return the display model for the viewer.""" +# return self._display_model + +# @model.setter +# def model(self, display_model: ArrayDisplayModel) -> None: +# """Set the display model for the viewer.""" +# display_model = ArrayDisplayModel.model_validate(display_model) +# previous_model: ArrayDisplayModel | None = getattr(self, "_display_model", None) +# if previous_model is not None: +# self._set_model_connected(previous_model, False) + +# self._display_model = display_model +# self._set_model_connected(display_model) + +# def _set_model_connected( +# self, model: ArrayDisplayModel, connect: bool = True +# ) -> None: +# """Connect or disconnect the model to/from the viewer. + +# We do this in a single method so that we are sure to connect and disconnect +# the same events in the same order. +# """ +# _connect = "connect" if connect else "disconnect" + +# for obj, callback in [ +# (model.events.visible_axes, self._on_visible_axes_changed), +# # the current_index attribute itself is immutable +# (model.current_index.value_changed, self._on_current_index_changed), +# (model.events.channel_axis, self._on_channel_axis_changed), +# # TODO: lut values themselves are mutable evented objects... +# # so we need to connect to their events as well +# (model.luts.value_changed, self._on_luts_changed), +# ]: +# getattr(obj, _connect)(callback) + +# def _on_visible_axes_changed( +# self, value: tuple[AxisKey, AxisKey, AxisKey] | tuple[AxisKey, AxisKey] +# ) -> None: +# self._update_visible_sliders() +# self._canvas.set_ndim(self.model.n_visible_axes) + +# def _update_visible_sliders(self) -> None: +# """Hide sliders corresponding to "visible" axes.""" +# if self._data_wrapper is None: +# return +# dims = self._data_wrapper.dims +# hide = {self._canonicalize_axis_key(ax, dims) for ax in self.model.visible_axes} +# self._dims_sliders.hide_dimensions(hide, show_remainder=True) + +# def _on_current_index_changed(self) -> None: +# value = self.model.current_index +# with signals_blocked(self._dims_sliders): +# self._dims_sliders.setValue(value) +# self._update_canvas() + +# def _on_sliders_value_changed(self) -> None: +# value = self._dims_sliders.value() +# self.model.current_index.update(value) + +# def _canonicalize_axis_key(self, axis: AxisKey, dims: Sequence[Hashable]) -> int: +# """Return positive index for AxisKey (which can be +/- int or label).""" +# # TODO: improve performance by indexing ahead of time +# if isinstance(axis, int): +# ndims = len(dims) +# ax = axis if axis >= 0 else len(dims) + axis +# if ax >= ndims: +# raise IndexError( +# f"Axis index {axis} out of bounds for data with {ndims} dimensions" +# ) +# return ax +# try: +# return dims.index(axis) +# except ValueError as e: +# raise IndexError(f"Axis label {axis} not found in data dimensions") from e + +# def _current_index_request(self) -> Mapping[int, int | slice]: +# # Generate cannocalized index request +# if self._data_wrapper is None: +# return {} + +# dims = self._data_wrapper.dims +# idx_request = { +# self._canonicalize_axis_key(ax, dims): v +# for ax, v in self.model.current_index.items() +# } +# for ax in self.model.visible_axes: +# ax_ = self._canonicalize_axis_key(ax, dims) +# if not isinstance(idx_request.get(ax_), slice): +# idx_request[ax_] = slice(None) +# return idx_request + +# def _update_canvas(self) -> None: +# # get the data from the data wrapper +# if self._data_wrapper is None: +# return +# idx_request = self._current_index_request() +# data = self._data_wrapper.isel(idx_request) +# if hdl := getattr(self, "_handle", None): +# hdl.remove() +# self._handle = self._canvas.add_image(data) +# self._canvas.set_range() + +# def _on_channel_axis_changed(self, value: AxisKey) -> None: +# print("Channel axis changed:", value) + +# def _on_luts_changed(self) -> None: +# print("LUTs changed", self.model.luts) diff --git a/x.py b/x.py deleted file mode 100644 index c0d8a33..0000000 --- a/x.py +++ /dev/null @@ -1,15 +0,0 @@ -import numpy as np -from qtpy.QtWidgets import QApplication - -from ndv.viewer_v2 import Viewer - -app = QApplication([]) - -v = Viewer() -v.data = np.random.rand(96, 64, 128).astype(np.float32) -v.model.luts[1] = "viridis" -v.model.visible_axes = (-2, -1) -# print(v.model) -v.show() -v.model.current_index.update({0: 3, 1: 32, 2: 12}) -app.exec()