Skip to content

Commit

Permalink
misc wip
Browse files Browse the repository at this point in the history
  • Loading branch information
tlambert03 committed Nov 6, 2024
1 parent a892977 commit e6de2dd
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 264 deletions.
13 changes: 6 additions & 7 deletions mvc.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "9d6de969d9034cc8aca2b4347b2ebad6",
"model_id": "ddb975849c044d2aa8c7d5b7b55f6bde",
"version_major": 2,
"version_minor": 0
},
Expand All @@ -22,7 +22,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "1aaf69f5944841a09e09911cb8d08ff4",
"model_id": "e64758a0a08f493f95bb52446c7853be",
"version_major": 2,
"version_minor": 0
},
Expand All @@ -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()"
]
},
{
Expand Down Expand Up @@ -90,7 +89,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.6"
"version": "3.12.7"
}
},
"nbformat": 4,
Expand Down
3 changes: 1 addition & 2 deletions mvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
47 changes: 39 additions & 8 deletions src/ndv/controller.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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: ...
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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
98 changes: 94 additions & 4 deletions src/ndv/v2view_jupyter.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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()
6 changes: 3 additions & 3 deletions src/ndv/v2view_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
Loading

0 comments on commit e6de2dd

Please sign in to comment.