Skip to content
Closed
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ repos:
files: "^src/"
additional_dependencies:
- numpy
- pydantic
- psygnal
- IPython
19 changes: 19 additions & 0 deletions examples/display_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from rich import print

from ndv.models._array_display_model import ArrayDisplayModel

m = ArrayDisplayModel()

m.events.channel_axis.connect(lambda x: print(f"channel_axis: {x}"))
m.current_index.item_added.connect(lambda x, y: print(f"current_index[{x}] = {y}"))
m.current_index.item_changed.connect(
lambda x, y, z: print(f"current_index[{x!r}] = {y} -> {z}")
)

m.channel_axis = 4
m.current_index["5"] = 1
m.current_index[5] = 4


assert ArrayDisplayModel.model_json_schema(mode="validation")
assert ArrayDisplayModel.model_json_schema(mode="serialization")
22 changes: 6 additions & 16 deletions examples/tensorstore_arr.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
from __future__ import annotations

import ndv

try:
import tensorstore as ts
from ndv.data import cosem_dataset

ts_array = cosem_dataset()
except ImportError:
raise ImportError("Please install tensorstore to run this example")


import ndv

ts_array = ts.open(
{
"driver": "n5",
"kvstore": {
"driver": "s3",
"bucket": "janelia-cosem-datasets",
"path": "jrc_hela-3/jrc_hela-3.n5/labels/er-mem_pred/s4/",
},
},
).result()
ts_array = ts_array[ts.d[:].label["z", "y", "x"]]
ndv.imshow(ts_array[ts.d[("y", "x", "z")].transpose[:]])
ndv.imshow(ts_array)
95 changes: 95 additions & 0 deletions mvc.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "fe399f0bdcd14727809310d83c141521",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"RFBOutputContext()"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "330f4a4ed41b4bef97c3d5e850026d33",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(CanvasBackend(css_height='600px', css_width='600px'), VBox(children=(IntSlider(value=0, descrip…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from ndv.controller import ViewerController\n",
"from ndv.data import cells3d\n",
"\n",
"viewer = ViewerController()\n",
"model = viewer.model\n",
"viewer.data = cells3d()\n",
"\n",
"viewer.view.show()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"viewer.model.visible_axes = (0, 3)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"viewer.model.default_lut.cmap = \"cubehelix\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "ndv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
16 changes: 16 additions & 0 deletions mvc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Example usage of new mvc pattern."""

import numpy as np
from qtpy.QtWidgets import QApplication

from ndv import data
from ndv.controller import ViewerController

app = QApplication([])

viewer = ViewerController() # ultimately, this will be the public api

# viewer.data = data.cosem_dataset(level=5)
viewer.data = np.tile(data.cells3d(), (2, 1, 3, 4))
viewer._view.show()
app.exec()
9 changes: 8 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ classifiers = [
"Programming Language :: Python :: 3.12",
"Typing :: Typed",
]
dependencies = ["qtpy", "numpy", "superqt[cmap,iconify]"]
dependencies = ["qtpy", "numpy", "superqt[cmap,iconify]", "pydantic", "psygnal"]

# https://peps.python.org/pep-0621/#dependencies-optional-dependencies
[project.optional-dependencies]
Expand Down Expand Up @@ -92,6 +92,7 @@ select = [
]
ignore = [
"D401", # First line should be in imperative mood
"D10", # Missing docstring...
]

[tool.ruff.lint.per-file-ignores]
Expand All @@ -112,6 +113,12 @@ disallow_any_generics = false
disallow_subclassing_any = false
show_error_codes = true
pretty = true
plugins = ["pydantic.mypy"]

# Alternatively, if _old_viewer is a submodule of ndv
[[tool.mypy.overrides]]
module = "ndv._old_viewer"
ignore_errors = true

# https://docs.pytest.org/
[tool.pytest.ini_options]
Expand Down
16 changes: 2 additions & 14 deletions src/ndv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,9 @@
__author__ = "Talley Lambert"
__email__ = "[email protected]"

from typing import TYPE_CHECKING

from . import data
from ._old_viewer import NDViewer
from .models import DataWrapper
from .util import imshow
from .viewer._data_wrapper import DataWrapper
from .viewer._viewer import NDViewer

__all__ = ["NDViewer", "DataWrapper", "imshow", "data"]


if TYPE_CHECKING:
# these may be used externally, but are not guaranteed to be available at runtime
# they must be used inside a TYPE_CHECKING block

from .viewer._dims_slider import DimKey as DimKey
from .viewer._dims_slider import Index as Index
from .viewer._dims_slider import Indices as Indices
from .viewer._dims_slider import Sizes as Sizes
21 changes: 12 additions & 9 deletions src/ndv/viewer/_viewer.py → src/ndv/_old_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,32 @@
from superqt import QCollapsible, QElidingLabel, QIconifyIcon, ensure_main_thread
from superqt.utils import qthrottled, signals_blocked

from ndv.viewer._components import (
from ndv.models._data_wrapperold import DataWrapper
from ndv.views import get_canvas_class
from ndv.views._qt._components import (
ChannelMode,
ChannelModeButton,
DimToggleButton,
QSpinner,
ROIButton,
)

from ._backends import get_canvas_class
from ._data_wrapper import DataWrapper
from ._dims_slider import DimsSliders
from ._lut_control import LutControl
from ndv.views._qt._dims_slider import DimsSliders
from ndv.views._qt._lut_control import LutControl

if TYPE_CHECKING:
from collections.abc import Hashable, Iterable, Sequence
from collections.abc import Hashable, Iterable, Mapping, Sequence
from concurrent.futures import Future
from typing import Any, Callable, TypeAlias

from qtpy.QtCore import QObject
from qtpy.QtGui import QCloseEvent, QKeyEvent

from ._backends._protocols import CanvasElement, PCanvas, PImageHandle, PRoiHandle
from ._dims_slider import DimKey, Indices, Sizes
DimKey = Hashable
Index = int | slice
Indices = Mapping[Hashable, Index]
Sizes = Mapping[Hashable, int]

from ndv.views.protocols import CanvasElement, PCanvas, PImageHandle, PRoiHandle

ImgKey: TypeAlias = Hashable
# any mapping of dimensions to sizes
Expand Down
40 changes: 40 additions & 0 deletions src/ndv/_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""General model for ndv."""

from collections.abc import Hashable, Sequence
from contextlib import suppress
from typing import Annotated, Any, TypeAlias

from pydantic import PlainValidator


def _maybe_int(val: Any) -> Any:
# try to convert to int if possible
with suppress(ValueError, TypeError):
val = int(float(val))
return val


def _to_slice(val: Any) -> slice:
# slices are returned as is
if isinstance(val, slice):
if not all(
isinstance(i, (int, type(None))) for i in (val.start, val.stop, val.step)
):
raise TypeError(f"Slice start/stop/step must all be integers: {val!r}")
return val
# single integers are converted to slices starting at that index
if isinstance(val, int):
return slice(val, val + 1)
# sequences are interpreted as arguments to the slice constructor
if isinstance(val, Sequence):
return slice(*(int(x) if x is not None else None for x in val))
raise TypeError(f"Expected int or slice, got {type(val)}")


Slice = Annotated[slice, PlainValidator(_to_slice)]

# An axis key is any hashable object that can be used to index an axis
# In many cases it will be an integer, but for some labeled arrays it may be a string
# or other hashable object. It is up to the DataWrapper to convert these keys to
# actual integer indices.
AxisKey: TypeAlias = Annotated[Hashable, PlainValidator(_maybe_int)]
3 changes: 3 additions & 0 deletions src/ndv/controller/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._controller import ViewerController

__all__ = ["ViewerController"]
Loading
Loading