Skip to content

Commit 5c070a6

Browse files
Switch to Cellier convenience API, add Jupyter Lab support (#17)
* bump cellier * change to convenience API * fix blobs * update readme and deps * add gui CI setup * update install instructions
1 parent a30a22a commit 5c070a6

18 files changed

Lines changed: 5284 additions & 3245 deletions

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ jobs:
4242
python-version: ${{ matrix.python-version }}
4343
enable-cache: true
4444

45+
- name: Set up headless GUI
46+
uses: pyvista/setup-headless-display-action@v4
47+
with:
48+
qt: true
49+
50+
- name: Install llvmpipe and lavapipe for offscreen canvas
51+
if: matrix.platform == 'ubuntu-latest'
52+
run: |
53+
sudo apt-get update -y -qq
54+
sudo apt install -y libegl1-mesa-dev libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
55+
4556
- name: Install Dependencies
4657
run: uv sync --no-dev --group test
4758

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ target/
7373
# Jupyter Notebook
7474
.ipynb_checkpoints
7575

76+
# Generated example datasets
77+
*.ome.zarr/
78+
cells3d.ome.zarr
79+
7680
# pyenv
7781
.python-version
7882

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,19 @@
66
[![CI](https://github.com/kevinyamauchi/oz-viewer/actions/workflows/ci.yml/badge.svg)](https://github.com/kevinyamauchi/oz-viewer/actions/workflows/ci.yml)
77
[![codecov](https://codecov.io/gh/kevinyamauchi/oz-viewer/branch/main/graph/badge.svg)](https://codecov.io/gh/kevinyamauchi/oz-viewer)
88

9+
> [!NOTE]
10+
> This is an early prototype. Many things may not be working and the API will change.
11+
912
A viewer for ome-zarr images.
1013

14+
## Installation
15+
16+
```sh
17+
uv pip install "oz-viewer[examples] @ git+https://github.com/kevinyamauchi/oz-viewer.git"
18+
19+
# if you want to try the Jupyter Lab viewer, install rendercanvas from main
20+
uv pip install git+https://github.com/pygfx/rendercanvas.git
21+
````
1122
## Single canvas viewer
1223

1324
You can load a v0.4 or v0.5 OME-Zarr file into a single-canvas 2d/3d viewer using the `oz-viewer view` CLI. See the example below. Replace the file path with the path to your image. This works for both local and remote data.
@@ -32,11 +43,15 @@ Example viewing https://livingobjects.ebi.ac.uk/idr/zarr/v0.5/idr0066/ExpA_VIP_A
3243

3344
https://github.com/user-attachments/assets/a6c0cab9-0cd9-4fe0-80c2-c77207b4fd77
3445

35-
You can click the multichannel button in the upper left-hand corner to toggle berween single/multichannel rendering. Example viewing the scikit-image cells3d (converted to ome-zarr) multichannel image
46+
You can click the multichannel button in the upper left-hand corner to toggle between single/multichannel rendering. Example viewing the scikit-image cells3d (converted to ome-zarr) multichannel image
3647

3748
https://github.com/user-attachments/assets/b6655863-b8fb-4eea-be84-eb031283494e
3849

50+
## Jupyter Lab
51+
52+
Both the viewer and orthoviewer can be used in Jupyter Lab. See the `examples/viewer.ipynb` and `examples/orthoviewer.ipynb` notebooks for examples. The viewer can be rendered as a sidecar widget next to the notebook.
3953

54+
https://github.com/user-attachments/assets/f572a57e-ab92-4bf4-8b4c-e6af46c9f6ed
4055

4156
## Test dataset latency
4257

examples/orthoviewer.ipynb

Lines changed: 1674 additions & 0 deletions
Large diffs are not rendered by default.

examples/viewer.ipynb

Lines changed: 806 additions & 0 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,31 @@ dependencies = [
3838
"typer >= 0.12",
3939
"rich >= 13",
4040
"numpy >= 1.24",
41-
"cellier[pyside]>=0.0.22",
42-
"jupyterlab>=4.5.6",
41+
"cellier[pyside,anywidget]>=0.0.25",
4342
"aiohttp >= 3.9",
4443
"zarr >= 3.0",
4544
"s3fs",
4645
"tensorstore",
46+
"sidecar>=0.8.1",
4747
]
4848

4949
# https://peps.python.org/pep-0621/#dependencies-optional-dependencies
5050
# add dependencies for "extra" features here. Not dev dependencies.
51-
# [project.optional-dependencies]
52-
# name = ["dependency"]
51+
[project.optional-dependencies]
52+
# Dependencies for the notebook examples under examples/. Install with
53+
# `pip install -e '.[examples]'`. cellier[anywidget] adds the anywidget
54+
# front-end (notebook rendering); scikit-image + pooch provide the cells3d
55+
# sample dataset used by examples/viewer.ipynb.
56+
#
57+
# NOTE: the anywidget canvas backend currently requires rendercanvas from git
58+
# main, which cannot be expressed as a version pin here -- install it
59+
# separately:
60+
# uv pip install "rendercanvas @ git+https://github.com/pygfx/rendercanvas.git"
61+
examples = [
62+
"jupyterlab>=4.5.6",
63+
"scikit-image>=0.24",
64+
"pooch",
65+
]
5366

5467
[project.urls]
5568
homepage = "https://github.com/kevinyamauchi/oz-viewer"
@@ -149,6 +162,9 @@ ignore = [
149162
[tool.typos.default]
150163
extend-ignore-identifiers-re = []
151164

165+
[tool.typos.files]
166+
extend-exclude = ["examples/*.ipynb"]
167+
152168
[tool.typos.default.extend-words]
153169
# OME (Open Microscopy Environment) is a domain term, not a typo for "SOME"
154170
OME = "OME"

src/oz_viewer/data/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
"""Example data for oz-viewer."""
2+
3+
from oz_viewer.data._blobs import make_example_zarr
4+
from oz_viewer.data._cells3d import make_cells3d_zarr
5+
6+
__all__ = [
7+
"make_cells3d_zarr",
8+
"make_example_zarr",
9+
]

src/oz_viewer/data/_blobs.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
_BLOB_RADIUS_UM = 120.0
1616
_CHUNK_ZYX = (32, 32, 32)
1717

18-
_DEFAULT_PATH = Path(__file__).parent / "example_anisotropic_blobs.ome.zarr"
18+
_DEFAULT_PATH = Path("example_anisotropic_blobs.ome.zarr")
1919

2020

2121
def _make_blob_volume(
@@ -62,7 +62,7 @@ def _make_blob_volume(
6262
return volume
6363

6464

65-
def make_example_zarr(output_path: Path = _DEFAULT_PATH) -> Path:
65+
def make_example_zarr(output_path: Path | str = _DEFAULT_PATH) -> Path:
6666
"""Create a synthetic anisotropic OME-Zarr with spherical blobs.
6767
6868
Uses the same Z/YX scale ratio as ExpA (5.0 : 6.55 µm). Only Y and X are
@@ -71,8 +71,11 @@ def make_example_zarr(output_path: Path = _DEFAULT_PATH) -> Path:
7171
7272
Parameters
7373
----------
74-
output_path : Path
75-
Directory to write the OME-Zarr store. Created if absent.
74+
output_path : Path or str
75+
Directory to write the OME-Zarr store. Defaults to
76+
``example_anisotropic_blobs.ome.zarr`` in the current working
77+
directory. If the path already exists it is left untouched and
78+
returned.
7679
7780
Returns
7881
-------

src/oz_viewer/data/_cells3d.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""Synthetic multichannel OME-Zarr example from ``skimage.data.cells3d``.
2+
3+
Writes a two-channel fluorescence volume (cell membranes + nuclei) as a
4+
3-level OME-Zarr v0.5 store with a channel axis, suitable for the multichannel
5+
viewer. The sample data is downloaded on first use via ``pooch`` (a
6+
``scikit-image`` dependency), so the first call needs network access.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
from pathlib import Path
12+
13+
import numpy as np
14+
15+
# Physical spacing (micrometers): z=0.29, y/x=0.26 at level 0, y/x halved per
16+
# level. c is a channel axis (unitless, scale 1.0).
17+
_SCALE_Z = 0.29
18+
_SCALE_YX_L0 = 0.26
19+
_N_LEVELS = 3
20+
_CHUNK_CZYX = (1, 1, 64, 64)
21+
22+
_DEFAULT_PATH = Path("cells3d.ome.zarr")
23+
24+
25+
def make_cells3d_zarr(output_path: Path | str = _DEFAULT_PATH) -> Path:
26+
"""Create a two-channel OME-Zarr from ``skimage.data.cells3d``.
27+
28+
The stored array is ordered ``(c, z, y, x)`` -- channel-first, as required
29+
by the OME-Zarr v0.5 axis-ordering rule -- with two channels (channel 0:
30+
membranes, channel 1: nuclei) and three resolution levels downsampled in
31+
``y``/``x`` only.
32+
33+
Parameters
34+
----------
35+
output_path : Path or str
36+
Directory to write the OME-Zarr store. Defaults to
37+
``cells3d.ome.zarr`` in the current working directory. If the path
38+
already exists it is left untouched and returned.
39+
40+
Returns
41+
-------
42+
Path
43+
Resolved path to the written (or pre-existing) store.
44+
"""
45+
import zarr
46+
from skimage.data import cells3d
47+
from skimage.measure import block_reduce
48+
49+
output_path = Path(output_path)
50+
if output_path.exists():
51+
print(f"Example dataset already exists at {output_path}")
52+
return output_path.resolve()
53+
54+
print(f"Creating multichannel cells3d dataset at {output_path} ...")
55+
print("Loading skimage.data.cells3d() (downloads on first use) ...")
56+
# skimage returns (z, c, y, x); transpose to (c, z, y, x) so the channel
57+
# axis precedes all spatial axes.
58+
data_l0 = np.transpose(cells3d(), (1, 0, 2, 3)).astype(np.uint16)
59+
60+
levels = [data_l0]
61+
for _ in range(_N_LEVELS - 1):
62+
down = block_reduce(levels[-1], block_size=(1, 1, 2, 2), func=np.mean).astype(
63+
np.uint16
64+
)
65+
levels.append(down)
66+
67+
root = zarr.open_group(str(output_path), mode="w")
68+
datasets_meta = []
69+
for level, arr in enumerate(levels):
70+
zarr_arr = root.create_array(
71+
str(level),
72+
shape=arr.shape,
73+
chunks=_CHUNK_CZYX,
74+
dtype=np.uint16,
75+
)
76+
zarr_arr[:] = arr
77+
yx = _SCALE_YX_L0 * (2.0**level)
78+
datasets_meta.append(
79+
{
80+
"path": str(level),
81+
"coordinateTransformations": [
82+
{"type": "scale", "scale": [1.0, _SCALE_Z, yx, yx]},
83+
],
84+
}
85+
)
86+
print(f" Level {level}: shape={arr.shape} scale=(z={_SCALE_Z}, yx={yx:.4f})")
87+
88+
root.attrs["ome"] = {
89+
"version": "0.5",
90+
"multiscales": [
91+
{
92+
"axes": [
93+
{"name": "c", "type": "channel"},
94+
{"name": "z", "type": "space", "unit": "micrometer"},
95+
{"name": "y", "type": "space", "unit": "micrometer"},
96+
{"name": "x", "type": "space", "unit": "micrometer"},
97+
],
98+
"datasets": datasets_meta,
99+
"name": "cells3d",
100+
}
101+
],
102+
}
103+
104+
print("Done. Two channels (0: membranes, 1: nuclei), 3 resolution levels.")
105+
return output_path.resolve()

src/oz_viewer/viewer/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
"""Viewer modules for oz-viewer."""
22

33
from oz_viewer.viewer._orthoviewer import (
4-
OmeZarrOrthoViewer,
5-
build_ortho_viewer_model,
4+
build_ortho_viewer,
5+
display_orthoviewer,
66
launch_orthoviewer,
77
orthoviewer,
88
)
99
from oz_viewer.viewer._viewer import (
10-
OmeZarrViewer,
11-
build_viewer_model,
10+
build_viewer,
11+
display_viewer,
1212
launch_viewer,
1313
viewer,
1414
)
1515

1616
__all__ = [
17-
"OmeZarrOrthoViewer",
18-
"OmeZarrViewer",
19-
"build_ortho_viewer_model",
20-
"build_viewer_model",
17+
"build_ortho_viewer",
18+
"build_viewer",
19+
"display_orthoviewer",
20+
"display_viewer",
2121
"launch_orthoviewer",
2222
"launch_viewer",
2323
"orthoviewer",

0 commit comments

Comments
 (0)