Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ repos:
- id: yamllint
exclude: pre-commit-config.yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.14.5"
rev: "v0.14.7"
hooks:
- id: ruff-format
- id: ruff-check
- repo: https://github.com/rhysd/actionlint
rev: v1.7.8
rev: v1.7.9
hooks:
- id: actionlint
- repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ oa_image.info()
oa_image.view(how="matplotlib")

# Display the image with pyvista
# (great for ZYX 3D images).
# (great for ZYX 3D images; install extras: `pip install 'ome-arrow[viz]'`).
oa_image.view(how="pyvista")

# Export to OME-Parquet.
Expand Down
1 change: 1 addition & 0 deletions docs/src/examples/learning_to_fly_with_ome-arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
stack

# we can visualize the stack using pyvista for 3D rendering
# (requires optional extras: pip install "ome-arrow[viz]")
stack.view(how="pyvista")

# here we demonstrate that the data can be exported again
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ dependencies = [
"bioio-ome-zarr>=3.0.3",
"bioio-tifffile>=1.3",
"fire>=0.7",
"ipywidgets>=8.1.8",
"jupyterlab-widgets>=3.0.16",
"matplotlib>=3.10.7",
"numpy>=2.2.6",
"pandas>=2.2.3",
"pillow>=12",
"pyarrow>=22",
]

optional-dependencies.viz = [
"ipywidgets>=8.1.8",
"jupyterlab-widgets>=3.0.16",
"pyvista>=0.46.4",
"trame>=3.12",
"trame-vtk>=2.10",
Expand Down
8 changes: 5 additions & 3 deletions src/ome_arrow/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
from __future__ import annotations

import pathlib
from typing import Any, Dict, Iterable, Optional, Tuple
from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple

import matplotlib
import numpy as np
import pyarrow as pa
import pyvista

from ome_arrow.export import to_numpy, to_ome_parquet, to_ome_tiff, to_ome_zarr
from ome_arrow.ingest import (
Expand All @@ -25,6 +24,9 @@
from ome_arrow.utils import describe_ome_arrow
from ome_arrow.view import view_matplotlib, view_pyvista

if TYPE_CHECKING:
import pyvista


class OMEArrow:
"""
Expand Down Expand Up @@ -293,7 +295,7 @@ def view(
clim: tuple[float, float] | None = None,
show_axes: bool = True,
scaling_values: tuple[float, float, float] | None = (1.0, 0.1, 0.1),
) -> matplotlib.figure.Figure | pyvista.Plotter:
) -> matplotlib.figure.Figure | "pyvista.Plotter":
"""
Render an OME-Arrow record using Matplotlib or PyVista.

Expand Down
34 changes: 29 additions & 5 deletions src/ome_arrow/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@
Viewing utilities for OME-Arrow data.
"""

from __future__ import annotations

import contextlib
import warnings
from typing import TYPE_CHECKING

import matplotlib.pyplot as plt
import numpy as np
import pyarrow as pa
import pyvista as pv
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from matplotlib.image import AxesImage

try: # optional dependency
import pyvista as pv
except ImportError: # pragma: no cover - exercised when viz extra missing
pv = None # type: ignore[assignment]

if TYPE_CHECKING:
import pyvista


def view_matplotlib(
data: dict[str, object] | pa.StructScalar,
Expand Down Expand Up @@ -63,6 +74,21 @@ def view_matplotlib(
return fig, ax, im


def _require_pyvista() -> "pyvista":
"""
Ensure PyVista is available, raising a helpful error otherwise.
"""
if pv is None:
msg = (
"PyVista-based visualization requires the optional 'viz' extras. "
"Install with `pip install ome-arrow[viz]` to enable 3D viewing."
)
warnings.warn(msg, RuntimeWarning)
raise RuntimeError(msg)

return pv


def view_pyvista(
data: dict | pa.StructScalar,
c: int = 0,
Expand All @@ -77,16 +103,14 @@ def view_pyvista(
percentile_clim: tuple[float, float] = (1.0, 99.9), # robust contrast
sampling_scale: float = 0.5, # smaller = denser rays (sharper, slower)
show: bool = True,
) -> pv.Plotter:
) -> "pyvista.Plotter":
"""
Jupyter-inline interactive volume view using PyVista backends.
Tries 'trame' → 'html' → 'static' when backend='auto'.

sampling_scale controls ray step via the mapper after add_volume.
"""
import warnings

import numpy as np
pv = _require_pyvista()

# ---- unwrap OME-Arrow row
row = data.as_py() if isinstance(data, pa.StructScalar) else data
Expand Down
5 changes: 4 additions & 1 deletion tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import matplotlib
import pytest
import pyvista as pv

from ome_arrow.core import OMEArrow

Expand Down Expand Up @@ -262,6 +261,10 @@ def test_ome_arrow_base_expectations(
oa_image.view(how="matplotlib", show=False)[0], matplotlib.figure.Figure
)

pv = pytest.importorskip(
"pyvista",
reason="PyVista visualization stack is optional (install extras: viz)",
)
assert isinstance(oa_image.view(how="pyvista", show=False), pv.Plotter)

# test info description consistency across data inputs
Expand Down
20 changes: 20 additions & 0 deletions tests/test_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Tests for visualization helpers.
"""

import pytest

from ome_arrow import view


def test_view_pyvista_requires_optional_extras(monkeypatch: pytest.MonkeyPatch) -> None:
"""
Ensure a clear warning/error is raised when PyVista deps are missing.
"""
monkeypatch.setattr(view, "pv", None)

with (
pytest.warns(RuntimeWarning, match=r"ome-arrow\[viz\]"),
pytest.raises(RuntimeError, match="PyVista-based visualization"),
):
view.view_pyvista(data={})
23 changes: 14 additions & 9 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.