ome-arrow 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ome_arrow/__init__.py +19 -0
- ome_arrow/_version.py +34 -0
- ome_arrow/core.py +492 -0
- ome_arrow/export.py +422 -0
- ome_arrow/ingest.py +932 -0
- ome_arrow/meta.py +90 -0
- ome_arrow/transform.py +182 -0
- ome_arrow/utils.py +83 -0
- ome_arrow/view.py +286 -0
- ome_arrow-0.0.2.dist-info/METADATA +34 -0
- ome_arrow-0.0.2.dist-info/RECORD +14 -0
- ome_arrow-0.0.2.dist-info/WHEEL +5 -0
- ome_arrow-0.0.2.dist-info/licenses/LICENSE +28 -0
- ome_arrow-0.0.2.dist-info/top_level.txt +1 -0
ome_arrow/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Init file for ome_arrow package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from ome_arrow._version import version as ome_arrow_version
|
|
6
|
+
from ome_arrow.core import OMEArrow
|
|
7
|
+
from ome_arrow.export import to_numpy, to_ome_parquet, to_ome_tiff, to_ome_zarr
|
|
8
|
+
from ome_arrow.ingest import (
|
|
9
|
+
from_numpy,
|
|
10
|
+
from_ome_parquet,
|
|
11
|
+
from_ome_zarr,
|
|
12
|
+
from_tiff,
|
|
13
|
+
to_ome_arrow,
|
|
14
|
+
)
|
|
15
|
+
from ome_arrow.meta import OME_ARROW_STRUCT, OME_ARROW_TAG_TYPE, OME_ARROW_TAG_VERSION
|
|
16
|
+
from ome_arrow.utils import describe_ome_arrow, verify_ome_arrow
|
|
17
|
+
from ome_arrow.view import view_matplotlib, view_pyvista
|
|
18
|
+
|
|
19
|
+
__version__ = ome_arrow_version
|
ome_arrow/_version.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.0.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 2)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
ome_arrow/core.py
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core of the ome_arrow package, used for classes and such.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import pathlib
|
|
8
|
+
from typing import Any, Dict, Iterable, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
import matplotlib
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pyarrow as pa
|
|
13
|
+
import pyvista
|
|
14
|
+
|
|
15
|
+
from ome_arrow.export import to_numpy, to_ome_parquet, to_ome_tiff, to_ome_zarr
|
|
16
|
+
from ome_arrow.ingest import (
|
|
17
|
+
from_numpy,
|
|
18
|
+
from_ome_parquet,
|
|
19
|
+
from_ome_zarr,
|
|
20
|
+
from_stack_pattern_path,
|
|
21
|
+
from_tiff,
|
|
22
|
+
)
|
|
23
|
+
from ome_arrow.meta import OME_ARROW_STRUCT
|
|
24
|
+
from ome_arrow.transform import slice_ome_arrow
|
|
25
|
+
from ome_arrow.utils import describe_ome_arrow
|
|
26
|
+
from ome_arrow.view import view_matplotlib, view_pyvista
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class OMEArrow:
|
|
30
|
+
"""
|
|
31
|
+
Small convenience toolkit for working with ome-arrow data.
|
|
32
|
+
|
|
33
|
+
If `input` is a TIFF path, this loads it via `tiff_to_ome_arrow`.
|
|
34
|
+
If `input` is a dict, it will be converted using `to_struct_scalar`.
|
|
35
|
+
If `input` is already a `pa.StructScalar`, it is used as-is.
|
|
36
|
+
|
|
37
|
+
In Jupyter, evaluating the instance will render the first plane using
|
|
38
|
+
matplotlib (via `_repr_html_`). Call `view_matplotlib()` to select a
|
|
39
|
+
specific (z, t, c) plane.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
input: TIFF path, nested dict, or `pa.StructScalar`.
|
|
43
|
+
struct: Expected Arrow StructType (e.g., OME_ARROW_STRUCT).
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
data: str | dict | pa.StructScalar | "np.ndarray",
|
|
49
|
+
tcz: Tuple[int, int, int] = (0, 0, 0),
|
|
50
|
+
) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Construct an OMEArrow from:
|
|
53
|
+
- a Bio-Formats-style stack pattern string (contains '<', '>', or '*')
|
|
54
|
+
- a path/URL to an OME-TIFF (.tif/.tiff)
|
|
55
|
+
- a path/URL to an OME-Zarr store (.zarr / .ome.zarr)
|
|
56
|
+
- a path/URL to an OME-Parquet file (.parquet / .pq)
|
|
57
|
+
- a NumPy ndarray (2D-5D; interpreted
|
|
58
|
+
with from_numpy defaults)
|
|
59
|
+
- a dict already matching the OME-Arrow schema
|
|
60
|
+
- a pa.StructScalar already typed to OME_ARROW_STRUCT
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
# set the tcz for viewing
|
|
64
|
+
self.tcz = tcz
|
|
65
|
+
|
|
66
|
+
# --- 1) Stack pattern (Bio-Formats-style) --------------------------------
|
|
67
|
+
if isinstance(data, str) and any(c in data for c in "<>*"):
|
|
68
|
+
self.data = from_stack_pattern_path(
|
|
69
|
+
data,
|
|
70
|
+
default_dim_for_unspecified="C",
|
|
71
|
+
map_series_to="T",
|
|
72
|
+
clamp_to_uint16=True,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# --- 2) String path/URL: OME-Zarr / OME-Parquet / OME-TIFF ---------------
|
|
76
|
+
elif isinstance(data, str):
|
|
77
|
+
s = data.strip()
|
|
78
|
+
path = pathlib.Path(s)
|
|
79
|
+
|
|
80
|
+
# Zarr detection
|
|
81
|
+
if (
|
|
82
|
+
s.lower().endswith(".zarr")
|
|
83
|
+
or s.lower().endswith(".ome.zarr")
|
|
84
|
+
or ".zarr/" in s.lower()
|
|
85
|
+
or (path.exists() and path.is_dir() and path.suffix.lower() == ".zarr")
|
|
86
|
+
):
|
|
87
|
+
self.data = from_ome_zarr(s)
|
|
88
|
+
|
|
89
|
+
# OME-Parquet
|
|
90
|
+
elif s.lower().endswith((".parquet", ".pq")) or path.suffix.lower() in {
|
|
91
|
+
".parquet",
|
|
92
|
+
".pq",
|
|
93
|
+
}:
|
|
94
|
+
self.data = from_ome_parquet(s)
|
|
95
|
+
|
|
96
|
+
# TIFF
|
|
97
|
+
elif path.suffix.lower() in {".tif", ".tiff"} or s.lower().endswith(
|
|
98
|
+
(".tif", ".tiff")
|
|
99
|
+
):
|
|
100
|
+
self.data = from_tiff(s)
|
|
101
|
+
|
|
102
|
+
elif path.exists() and path.is_dir():
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"Directory '{s}' exists but does not look like an OME-Zarr store "
|
|
105
|
+
"(expected suffix '.zarr' or '.ome.zarr')."
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
"String input must be one of:\n"
|
|
110
|
+
" • Bio-Formats pattern string (contains '<', '>' or '*')\n"
|
|
111
|
+
" • OME-Zarr path/URL ending with '.zarr' or '.ome.zarr'\n"
|
|
112
|
+
" • OME-Parquet file ending with '.parquet' or '.pq'\n"
|
|
113
|
+
" • OME-TIFF path/URL ending with '.tif' or '.tiff'"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# --- 3) NumPy ndarray ----------------------------------------------------
|
|
117
|
+
elif isinstance(data, np.ndarray):
|
|
118
|
+
# Uses from_numpy defaults: dim_order="TCZYX", clamp_to_uint16=True, etc.
|
|
119
|
+
# If the array is YX/ZYX/CYX/etc.,
|
|
120
|
+
# from_numpy will expand/reorder accordingly.
|
|
121
|
+
self.data = from_numpy(data)
|
|
122
|
+
|
|
123
|
+
# --- 4) Already-typed Arrow scalar ---------------------------------------
|
|
124
|
+
elif isinstance(data, pa.StructScalar):
|
|
125
|
+
self.data = data
|
|
126
|
+
|
|
127
|
+
# --- 5) Plain dict matching the schema -----------------------------------
|
|
128
|
+
elif isinstance(data, dict):
|
|
129
|
+
self.data = pa.scalar(data, type=OME_ARROW_STRUCT)
|
|
130
|
+
|
|
131
|
+
# --- otherwise ------------------------------------------------------------
|
|
132
|
+
else:
|
|
133
|
+
raise TypeError(
|
|
134
|
+
"input data must be str, dict, pa.StructScalar, or numpy.ndarray"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def export(
|
|
138
|
+
self,
|
|
139
|
+
how: str = "numpy",
|
|
140
|
+
dtype: np.dtype = np.uint16,
|
|
141
|
+
strict: bool = True,
|
|
142
|
+
clamp: bool = False,
|
|
143
|
+
*,
|
|
144
|
+
# common writer args
|
|
145
|
+
out: str | None = None,
|
|
146
|
+
dim_order: str = "TCZYX",
|
|
147
|
+
# OME-TIFF args
|
|
148
|
+
compression: str | None = "zlib",
|
|
149
|
+
compression_level: int = 6,
|
|
150
|
+
tile: tuple[int, int] | None = None,
|
|
151
|
+
# OME-Zarr args
|
|
152
|
+
chunks: tuple[int, int, int, int, int] | None = None, # (T,C,Z,Y,X)
|
|
153
|
+
zarr_compressor: str | None = "zstd",
|
|
154
|
+
zarr_level: int = 7,
|
|
155
|
+
# optional display metadata (both paths guard/ignore if unsafe)
|
|
156
|
+
use_channel_colors: bool = False,
|
|
157
|
+
# Parquet args
|
|
158
|
+
parquet_column_name: str = "ome_arrow",
|
|
159
|
+
parquet_compression: str | None = "zstd",
|
|
160
|
+
parquet_metadata: dict[str, str] | None = None,
|
|
161
|
+
) -> np.array | dict | pa.StructScalar | str:
|
|
162
|
+
"""
|
|
163
|
+
Export the OME-Arrow content in a chosen representation.
|
|
164
|
+
|
|
165
|
+
Args
|
|
166
|
+
----
|
|
167
|
+
how:
|
|
168
|
+
"numpy" → TCZYX np.ndarray
|
|
169
|
+
"dict" → plain Python dict
|
|
170
|
+
"scalar" → pa.StructScalar (as-is)
|
|
171
|
+
"ome-tiff" → write OME-TIFF via BioIO
|
|
172
|
+
"ome-zarr" → write OME-Zarr (OME-NGFF) via BioIO
|
|
173
|
+
"parquet" → write a single-row Parquet with one struct column
|
|
174
|
+
dtype:
|
|
175
|
+
Target dtype for "numpy"/writers (default: np.uint16).
|
|
176
|
+
strict:
|
|
177
|
+
For "numpy": raise if a plane has wrong pixel length.
|
|
178
|
+
clamp:
|
|
179
|
+
For "numpy"/writers: clamp values into dtype range before cast.
|
|
180
|
+
|
|
181
|
+
Keyword-only (writer specific)
|
|
182
|
+
------------------------------
|
|
183
|
+
out:
|
|
184
|
+
Output path (required for 'ome-tiff', 'ome-zarr', and 'parquet').
|
|
185
|
+
dim_order:
|
|
186
|
+
Axes string for BioIO writers; default "TCZYX".
|
|
187
|
+
compression / compression_level / tile:
|
|
188
|
+
OME-TIFF options (passed through to tifffile via BioIO).
|
|
189
|
+
chunks / zarr_compressor / zarr_level :
|
|
190
|
+
OME-Zarr options (chunk shape, compressor hint, level).
|
|
191
|
+
use_channel_colors:
|
|
192
|
+
Try to embed per-channel display colors when safe; otherwise omitted.
|
|
193
|
+
parquet_*:
|
|
194
|
+
Options for Parquet export (column name, compression, file metadata).
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
Any
|
|
199
|
+
- "numpy": np.ndarray (T, C, Z, Y, X)
|
|
200
|
+
- "dict": dict
|
|
201
|
+
- "scalar": pa.StructScalar
|
|
202
|
+
- "ome-tiff": output path (str)
|
|
203
|
+
- "ome-zarr": output path (str)
|
|
204
|
+
- "parquet": output path (str)
|
|
205
|
+
|
|
206
|
+
Raises
|
|
207
|
+
------
|
|
208
|
+
ValueError:
|
|
209
|
+
Unknown 'how' or missing required params.
|
|
210
|
+
"""
|
|
211
|
+
# existing modes
|
|
212
|
+
if how == "numpy":
|
|
213
|
+
return to_numpy(self.data, dtype=dtype, strict=strict, clamp=clamp)
|
|
214
|
+
if how == "dict":
|
|
215
|
+
return self.data.as_py()
|
|
216
|
+
if how == "scalar":
|
|
217
|
+
return self.data
|
|
218
|
+
|
|
219
|
+
mode = how.lower().replace("_", "-")
|
|
220
|
+
|
|
221
|
+
# OME-TIFF via BioIO
|
|
222
|
+
if mode in {"ome-tiff", "ometiff", "tiff"}:
|
|
223
|
+
if not out:
|
|
224
|
+
raise ValueError("export(how='ome-tiff') requires 'out' path.")
|
|
225
|
+
to_ome_tiff(
|
|
226
|
+
self.data,
|
|
227
|
+
out,
|
|
228
|
+
dtype=dtype,
|
|
229
|
+
clamp=clamp,
|
|
230
|
+
dim_order=dim_order,
|
|
231
|
+
compression=compression,
|
|
232
|
+
compression_level=int(compression_level),
|
|
233
|
+
tile=tile,
|
|
234
|
+
use_channel_colors=use_channel_colors,
|
|
235
|
+
)
|
|
236
|
+
return out
|
|
237
|
+
|
|
238
|
+
# OME-Zarr via BioIO
|
|
239
|
+
if mode in {"ome-zarr", "omezarr", "zarr"}:
|
|
240
|
+
if not out:
|
|
241
|
+
raise ValueError("export(how='ome-zarr') requires 'out' path.")
|
|
242
|
+
to_ome_zarr(
|
|
243
|
+
self.data,
|
|
244
|
+
out,
|
|
245
|
+
dtype=dtype,
|
|
246
|
+
clamp=clamp,
|
|
247
|
+
dim_order=dim_order,
|
|
248
|
+
chunks=chunks,
|
|
249
|
+
compressor=zarr_compressor,
|
|
250
|
+
compressor_level=int(zarr_level),
|
|
251
|
+
)
|
|
252
|
+
return out
|
|
253
|
+
|
|
254
|
+
# Parquet (single row, single struct column)
|
|
255
|
+
if mode in {"ome-parquet", "omeparquet", "parquet"}:
|
|
256
|
+
if not out:
|
|
257
|
+
raise ValueError("export(how='parquet') requires 'out' path.")
|
|
258
|
+
to_ome_parquet(
|
|
259
|
+
data=self.data,
|
|
260
|
+
out_path=out,
|
|
261
|
+
column_name=parquet_column_name,
|
|
262
|
+
compression=parquet_compression, # default 'zstd'
|
|
263
|
+
file_metadata=parquet_metadata,
|
|
264
|
+
)
|
|
265
|
+
return out
|
|
266
|
+
|
|
267
|
+
raise ValueError(f"Unknown export method: {how}")
|
|
268
|
+
|
|
269
|
+
def info(self) -> Dict[str, Any]:
|
|
270
|
+
"""
|
|
271
|
+
Describe the OME-Arrow data structure.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
dict with keys:
|
|
275
|
+
- shape: (T, C, Z, Y, X)
|
|
276
|
+
- type: classification string
|
|
277
|
+
- summary: human-readable text
|
|
278
|
+
"""
|
|
279
|
+
return describe_ome_arrow(self.data)
|
|
280
|
+
|
|
281
|
+
def view(
|
|
282
|
+
self,
|
|
283
|
+
how: str = "matplotlib",
|
|
284
|
+
tcz: tuple[int, int, int] = (0, 0, 0),
|
|
285
|
+
autoscale: bool = True,
|
|
286
|
+
vmin: int | None = None,
|
|
287
|
+
vmax: int | None = None,
|
|
288
|
+
cmap: str = "gray",
|
|
289
|
+
show: bool = True,
|
|
290
|
+
c: int | None = None,
|
|
291
|
+
downsample: int = 1,
|
|
292
|
+
opacity: str | float = "sigmoid",
|
|
293
|
+
clim: tuple[float, float] | None = None,
|
|
294
|
+
show_axes: bool = True,
|
|
295
|
+
scaling_values: tuple[float, float, float] | None = (1.0, 0.1, 0.1),
|
|
296
|
+
) -> matplotlib.figure.Figure | pyvista.Plotter:
|
|
297
|
+
"""
|
|
298
|
+
Render an OME-Arrow record using Matplotlib or PyVista.
|
|
299
|
+
|
|
300
|
+
This convenience method supports two rendering backends:
|
|
301
|
+
|
|
302
|
+
* ``how="matplotlib"`` — renders a single (t, c, z) plane as a 2D image.
|
|
303
|
+
Returns a Matplotlib :class:`~matplotlib.figure.Figure` (or whatever
|
|
304
|
+
:func:`view_matplotlib` returns) and optionally displays it with
|
|
305
|
+
``plt.show()`` when ``show=True``.
|
|
306
|
+
|
|
307
|
+
* ``how="pyvista"`` — creates an interactive 3D PyVista visualization in
|
|
308
|
+
Jupyter. When ``show=True``, displays the widget. Independently, a static
|
|
309
|
+
PNG snapshot is embedded in the notebook (inside a collapsed
|
|
310
|
+
``<details>`` block) for non-interactive renderers (e.g., GitHub).
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
how: Rendering backend. One of ``"matplotlib"`` or ``"pyvista"``.
|
|
314
|
+
tcz: The (t, c, z) indices of the plane to display when using Matplotlib.
|
|
315
|
+
Defaults to ``(0, 0, 0)``.
|
|
316
|
+
autoscale: If ``True`` and ``vmin``/``vmax`` are not provided, infer
|
|
317
|
+
display limits from the image data range (Matplotlib path only).
|
|
318
|
+
vmin: Lower display limit for intensity scaling (Matplotlib path only).
|
|
319
|
+
vmax: Upper display limit for intensity scaling (Matplotlib path only).
|
|
320
|
+
cmap: Matplotlib colormap name for single-channel display (Matplotlib only).
|
|
321
|
+
show: Whether to display the plot immediately. For Matplotlib, calls
|
|
322
|
+
``plt.show()``. For PyVista, calls ``plotter.show()``.
|
|
323
|
+
c: Channel index override for the PyVista view. If ``None``, uses
|
|
324
|
+
``tcz[1]`` (the ``c`` from ``tcz``).
|
|
325
|
+
downsample: Integer downsampling factor for the PyVista volume or slices.
|
|
326
|
+
Must be ``>= 1``.
|
|
327
|
+
opacity: Opacity specification for PyVista. Either a float in ``[0, 1]``
|
|
328
|
+
or the string ``"sigmoid"`` (backend interprets as a preset transfer
|
|
329
|
+
function).
|
|
330
|
+
clim: Contrast limits (``(low, high)``) for PyVista rendering.
|
|
331
|
+
show_axes: If ``True``, display axes in the PyVista scene.
|
|
332
|
+
scaling_values: Physical scale multipliers for the (x, y, z) axes used by
|
|
333
|
+
PyVista, typically to express anisotropy. Defaults to ``(1.0, 0.1, 0.1)``.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
matplotlib.figure.Figure | pyvista.Plotter:
|
|
337
|
+
* If ``how="matplotlib"``, returns the figure created by
|
|
338
|
+
:func:`view_matplotlib` (often a :class:`~matplotlib.figure.Figure`).
|
|
339
|
+
* If ``how="pyvista"``, returns the created :class:`pyvista.Plotter`.
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
ValueError: If a requested plane (``t,c,z``) is not found or if pixel
|
|
343
|
+
array dimensions are inconsistent (propagated from
|
|
344
|
+
:func:`view_matplotlib`).
|
|
345
|
+
TypeError: If parameter types are invalid (e.g., negative ``downsample``).
|
|
346
|
+
|
|
347
|
+
Notes:
|
|
348
|
+
* The PyVista path embeds a static PNG snapshot via Pillow (``PIL``). If
|
|
349
|
+
Pillow is unavailable, the method logs a warning and skips the snapshot,
|
|
350
|
+
but the interactive viewer is still returned.
|
|
351
|
+
* When ``show=False`` and ``how="pyvista"``, no interactive window is
|
|
352
|
+
opened, but the returned :class:`pyvista.Plotter` can be shown later.
|
|
353
|
+
|
|
354
|
+
Examples:
|
|
355
|
+
Display a single plane with Matplotlib:
|
|
356
|
+
|
|
357
|
+
>>> fig = obj.view(how="matplotlib", tcz=(0, 1, 5), cmap="magma")
|
|
358
|
+
|
|
359
|
+
Create an interactive PyVista scene in a Jupyter notebook:
|
|
360
|
+
|
|
361
|
+
>>> plotter = obj.view(how="pyvista", c=0, downsample=2, show=True)
|
|
362
|
+
|
|
363
|
+
Configure PyVista contrast limits and keep axes hidden:
|
|
364
|
+
|
|
365
|
+
>>> plotter = obj.view(how="pyvista", clim=(100, 2000), show_axes=False)
|
|
366
|
+
"""
|
|
367
|
+
if how == "matplotlib":
|
|
368
|
+
return view_matplotlib(
|
|
369
|
+
self.data,
|
|
370
|
+
tcz=tcz,
|
|
371
|
+
autoscale=autoscale,
|
|
372
|
+
vmin=vmin,
|
|
373
|
+
vmax=vmax,
|
|
374
|
+
cmap=cmap,
|
|
375
|
+
show=show,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
if how == "pyvista":
|
|
379
|
+
import base64
|
|
380
|
+
import io
|
|
381
|
+
|
|
382
|
+
from IPython.display import HTML, display
|
|
383
|
+
|
|
384
|
+
c_idx = int(tcz[1] if c is None else c)
|
|
385
|
+
plotter = view_pyvista(
|
|
386
|
+
data=self.data,
|
|
387
|
+
c=c_idx,
|
|
388
|
+
downsample=downsample,
|
|
389
|
+
opacity=opacity,
|
|
390
|
+
clim=clim,
|
|
391
|
+
show_axes=show_axes,
|
|
392
|
+
scaling_values=scaling_values,
|
|
393
|
+
show=False,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# 1) show the interactive widget for live work
|
|
397
|
+
if show:
|
|
398
|
+
plotter.show()
|
|
399
|
+
|
|
400
|
+
# 2) capture a PNG and embed it in a collapsed details block
|
|
401
|
+
try:
|
|
402
|
+
img = plotter.screenshot(return_img=True) # ndarray
|
|
403
|
+
if img is not None:
|
|
404
|
+
buf = io.BytesIO()
|
|
405
|
+
# use matplotlib-free writer: PyVista returns RGB(A) uint8
|
|
406
|
+
from PIL import (
|
|
407
|
+
Image as PILImage,
|
|
408
|
+
) # pillow is a light dep most envs have
|
|
409
|
+
|
|
410
|
+
PILImage.fromarray(img).save(buf, format="PNG")
|
|
411
|
+
b64 = base64.b64encode(buf.getvalue()).decode("ascii")
|
|
412
|
+
display(
|
|
413
|
+
HTML(
|
|
414
|
+
f"""
|
|
415
|
+
<details>
|
|
416
|
+
<summary>Static snapshot (for non-interactive view)</summary>
|
|
417
|
+
<img src="data:image/png;base64,{b64}" />
|
|
418
|
+
</details>
|
|
419
|
+
"""
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
except Exception as e:
|
|
423
|
+
print(f"Warning: could not save PyVista snapshot: {e}")
|
|
424
|
+
|
|
425
|
+
return plotter
|
|
426
|
+
|
|
427
|
+
def slice(
|
|
428
|
+
self,
|
|
429
|
+
x_min: int,
|
|
430
|
+
x_max: int,
|
|
431
|
+
y_min: int,
|
|
432
|
+
y_max: int,
|
|
433
|
+
t_indices: Optional[Iterable[int]] = None,
|
|
434
|
+
c_indices: Optional[Iterable[int]] = None,
|
|
435
|
+
z_indices: Optional[Iterable[int]] = None,
|
|
436
|
+
fill_missing: bool = True,
|
|
437
|
+
) -> OMEArrow:
|
|
438
|
+
"""
|
|
439
|
+
Create a cropped copy of an OME-Arrow record.
|
|
440
|
+
|
|
441
|
+
Crops spatially to [y_min:y_max, x_min:x_max] (half-open) and, if provided,
|
|
442
|
+
filters/reindexes T/C/Z to the given index sets.
|
|
443
|
+
|
|
444
|
+
Parameters
|
|
445
|
+
----------
|
|
446
|
+
x_min, x_max, y_min, y_max : int
|
|
447
|
+
Half-open crop bounds in pixels (0-based).
|
|
448
|
+
t_indices, c_indices, z_indices : Iterable[int] | None
|
|
449
|
+
Optional explicit indices to keep for T, C, Z. If None, keep all.
|
|
450
|
+
Selected indices are reindexed to 0..len-1 in the output.
|
|
451
|
+
fill_missing : bool
|
|
452
|
+
If True, any missing (t,c,z) planes in the selection are zero-filled.
|
|
453
|
+
|
|
454
|
+
Returns
|
|
455
|
+
-------
|
|
456
|
+
OMEArrow object
|
|
457
|
+
New OME-Arrow record with updated sizes and planes.
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
return OMEArrow(
|
|
461
|
+
data=slice_ome_arrow(
|
|
462
|
+
data=self.data,
|
|
463
|
+
x_min=x_min,
|
|
464
|
+
x_max=x_max,
|
|
465
|
+
y_min=y_min,
|
|
466
|
+
y_max=y_max,
|
|
467
|
+
t_indices=t_indices,
|
|
468
|
+
c_indices=c_indices,
|
|
469
|
+
z_indices=z_indices,
|
|
470
|
+
fill_missing=fill_missing,
|
|
471
|
+
)
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
def _repr_html_(self) -> str:
|
|
475
|
+
"""
|
|
476
|
+
Auto-render a plane as inline PNG in Jupyter.
|
|
477
|
+
"""
|
|
478
|
+
try:
|
|
479
|
+
view_matplotlib(
|
|
480
|
+
data=self.data,
|
|
481
|
+
tcz=self.tcz,
|
|
482
|
+
autoscale=True,
|
|
483
|
+
vmin=None,
|
|
484
|
+
vmax=None,
|
|
485
|
+
cmap="gray",
|
|
486
|
+
show=False,
|
|
487
|
+
)
|
|
488
|
+
# return blank string to avoid showing class representation below image
|
|
489
|
+
return self.info()["summary"]
|
|
490
|
+
except Exception as e:
|
|
491
|
+
# Fallback to a tiny text status if rendering fails.
|
|
492
|
+
return f"<pre>OMEArrowKit: render failed: {e}</pre>"
|