Skip to content

Commit

Permalink
refactor: Merge pull request pyapp-kit#82 from pyapp-kit/v2-mvc
Browse files Browse the repository at this point in the history
refactor: merge in v2 "MVC" branch
  • Loading branch information
tlambert03 authored Jan 9, 2025
2 parents cbd5460 + be0bc05 commit 48d06d4
Show file tree
Hide file tree
Showing 75 changed files with 6,079 additions and 1,997 deletions.
89 changes: 78 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,101 @@ jobs:
- run: pipx run check-manifest

test:
uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2
with:
os: ${{ matrix.os }}
python-version: ${{ matrix.python-version }}
coverage-upload: artifact
qt: pyqt6
name: ${{ matrix.os }} py${{ matrix.python-version }} ${{ matrix.gui }} ${{ matrix.canvas }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest, windows-latest]
# using 3.12 as main current version, until 3.13 support
# is ubiquitous in upstream dependencies
python-version: ["3.10", "3.12"]
gui: [pyside, pyqt, jup, wxpython]
canvas: [vispy, pygfx]
exclude:
# unsolved intermittent segfaults on this combo
- python-version: "3.10"
gui: pyside
# wxpython does not build wheels for ubuntu or macos-latest py3.10
- os: ubuntu-latest
python-version: "3.11" # unknown CI segfault...
gui: wxpython
- os: macos-latest
gui: wxpython
python-version: "3.10"
include:
# test a couple more python variants, without
# full os/gui/canvas matrix coverage
- os: ubuntu-latest
python-version: "3.13"
gui: jup
canvas: vispy
- os: ubuntu-latest
python-version: "3.13"
gui: jup
canvas: pygfx
# pyside6 is struggling with 3.9
- os: ubuntu-latest
python-version: "3.9"
gui: pyqt
canvas: vispy
- os: macos-13
gui: wxpython
python-version: "3.9"
canvas: vispy
- os: windows-latest
gui: jup
python-version: "3.9"
canvas: pygfx

steps:
- uses: actions/checkout@v4
- name: 🐍 Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"

- name: 📦 Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e '.[test,${{ matrix.gui }},${{ matrix.canvas }}]'
- uses: pyvista/setup-headless-display-action@v3
with:
qt: ${{ matrix.gui == 'pyside' || matrix.gui == 'pyqt' }}

- name: Install llvmpipe and lavapipe for offscreen canvas
if: matrix.os == 'ubuntu-latest' && matrix.canvas == 'pygfx'
run: |
sudo apt-get update -y -qq
sudo apt install -y libegl1-mesa-dev libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
- name: install pytest-qt
if: matrix.gui == 'pyside' || matrix.gui == 'pyqt'
run: pip install pytest-qt

- name: 🧪 Test
run: |
pytest --cov --cov-report=xml -v --color yes tests
- uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

test-array-libs:
uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2
with:
os: ${{ matrix.os }}
python-version: ${{ matrix.python-version }}
extras: "test,third_party_arrays"
extras: "test,vispy,third_party_arrays"
coverage-upload: artifact
pip-post-installs: "pytest-qt"
qt: pyqt6
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.9", "3.12"]
python-version: ["3.10", "3.12"]

upload_coverage:
if: always()
Expand Down
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
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,25 @@ See examples for each of these array types in [examples](./examples/)
## Installation

To just get started using Qt and vispy:

```python
pip install ndv[qt]
```

For Jupyter, without requiring Qt, you can use:

```python
pip install ndv[jupyter]
```

If you'd like more control over the backend, you can install the optional dependencies directly.

The only required dependencies are `numpy` and `superqt[cmap,iconify]`.
You will also need a Qt backend (PyQt or PySide) and one of either
[vispy](https://github.com/vispy/vispy) or [pygfx](https://github.com/pygfx/pygfx),
which can be installed through extras `ndv[<pyqt|pyside>,<vispy|pygfx>]`:

```python
pip install ndv[pyqt,vispy]
```

> [!TIP]
> If you have both vispy and pygfx installed, `ndv` will default to using vispy,
> but you can override this with the environment variable
Expand Down
32 changes: 25 additions & 7 deletions examples/custom_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import ndv

if TYPE_CHECKING:
from ndv import Indices, Sizes
from collections.abc import Hashable, Mapping, Sequence


class MyArrayThing:
"""Some custom data type that we want to visualize."""

def __init__(self, shape: tuple[int, ...]) -> None:
self.shape = shape
self._data = np.random.randint(0, 256, shape)
self._data = np.random.randint(0, 256, shape).astype(np.uint16)

def __getitem__(self, item: Any) -> np.ndarray:
return self._data[item] # type: ignore [no-any-return]
Expand All @@ -22,16 +24,32 @@ def __getitem__(self, item: Any) -> np.ndarray:
class MyWrapper(ndv.DataWrapper[MyArrayThing]):
@classmethod
def supports(cls, data: Any) -> bool:
"""Return True if the data is supported by this wrapper"""
if isinstance(data, MyArrayThing):
return True
return False

def sizes(self) -> Sizes:
"""Return a mapping of {dim: size} for the data"""
return {f"dim_{k}": v for k, v in enumerate(self.data.shape)}
@property
def dims(self) -> tuple[Hashable, ...]:
"""Return the dimensions of the data"""
return tuple(f"dim_{k}" for k in range(len(self.data.shape)))

@property
def coords(self) -> dict[Hashable, Sequence]:
"""Return a mapping of {dim: coords} for the data"""
return {f"dim_{k}": range(v) for k, v in enumerate(self.data.shape)}

@property
def dtype(self) -> np.dtype:
"""Return the dtype of the data"""
return self.data._data.dtype

def isel(self, indexers: Mapping[int, int | slice]) -> np.ndarray:
"""Select a subset of the data.
def isel(self, indexers: Indices) -> Any:
"""Convert mapping of {dim: index} to conventional indexing"""
`indexers` is a mapping of {dim: index} where index is either an integer or a
slice.
"""
idx = tuple(indexers.get(k, slice(None)) for k in range(len(self.data.shape)))
return self.data[idx]

Expand Down
82 changes: 0 additions & 82 deletions examples/histogram.py

This file was deleted.

86 changes: 86 additions & 0 deletions examples/notebook.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "f2fe7ce4f36847ffaba8c042bf85b76d",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"RFBOutputContext()"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "92985e7df82f47cabfe3ab5ce9ab0498",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Label(value='.ndarray (60, 2, 256, 256), uint16, 15.73MB'), CanvasBackend(css_height='600px', c…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from ndv import data, imshow\n",
"\n",
"viewer = imshow(data.cells3d())\n",
"viewer.model.channel_mode = \"composite\"\n",
"viewer.model.current_index.update({0: 32})"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"viewer.model.visible_axes = (0, 3)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"viewer.model.default_lut.cmap = \"cubehelix\"\n",
"viewer.model.channel_mode = \"grayscale\""
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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
}
2 changes: 1 addition & 1 deletion examples/numpy_arr.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
print(e)
img = ndv.data.nd_sine_wave((10, 3, 8, 512, 512))

ndv.imshow(img)
viewer = ndv.imshow(img)
Loading

0 comments on commit 48d06d4

Please sign in to comment.