Skip to content

Commit ecaa868

Browse files
authored
Merge pull request #543 from thewtex/python-37
ENH: Python 3.7 support
2 parents 2bcbced + 52e3236 commit ecaa868

File tree

6 files changed

+106
-99
lines changed

6 files changed

+106
-99
lines changed

.github/workflows/notebook-test.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,29 @@ jobs:
66
run:
77
runs-on: ubuntu-latest
88
name: Test notebooks with nbmake
9+
strategy:
10+
matrix:
11+
python-version: ['3.7', '3.8', '3.9', '3.10']
912
steps:
1013
- uses: actions/checkout@v3
1114

12-
- uses: actions/setup-python@v3
15+
- name: Set up Python ${{ matrix.python-version }}
16+
uses: actions/setup-python@v3
1317
with:
14-
python-version: '3.9'
18+
python-version: ${{ matrix.python-version }}
19+
1520
- uses: actions/setup-java@v3
1621
with:
1722
java-version: '8'
1823
distribution: 'zulu'
24+
1925
- name: Install test dependencies
2026
run: |
2127
python3 -m pip install --upgrade pip
2228
python3 -m pip install -e ".[test]"
2329
python3 -m pip install pyimagej
2430
python3 -c "import imagej; ij = imagej.init('2.5.0'); print(ij.getVersion())"
31+
2532
- name: Test notebooks
2633
run: |
2734
pytest --nbmake --nbmake-timeout=3000 examples/*.ipynb examples/integrations/dask/*.ipynb

itkwidgets/integrations/__init__.py

Lines changed: 84 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
from collections.abc import MutableMapping
2-
from re import S
3-
41
import itkwasm
52
import numpy as np
63
import zarr
7-
from multiscale_spatial_image import MultiscaleSpatialImage, to_multiscale, itk_image_to_multiscale, Methods
8-
from spatial_image import to_spatial_image, is_spatial_image
4+
from ngff_zarr import to_multiscales, to_ngff_zarr, to_ngff_image, itk_image_to_ngff_image, Methods
95

106
import dask
11-
import xarray as xr
12-
from .itk import HAVE_ITK, itk_image_to_wasm_image, itk_group_spatial_object_to_wasm_point_set
7+
from .itk import HAVE_ITK, itk_group_spatial_object_to_wasm_point_set
138
from .pytorch import HAVE_TORCH
14-
from .vtk import HAVE_VTK, vtk_image_to_spatial_image, vtk_polydata_to_vtkjs
15-
from .xarray import xarray_data_array_to_numpy, xarray_data_set_to_numpy
9+
from .vtk import HAVE_VTK, vtk_image_to_ngff_image, vtk_polydata_to_vtkjs
10+
from .xarray import HAVE_XARRAY, HAVE_MULTISCALE_SPATIAL_IMAGE, xarray_data_array_to_numpy, xarray_data_set_to_numpy
1611
from ..render_types import RenderType
1712

1813
def _spatial_image_scale_factors(spatial_image, min_length):
@@ -36,99 +31,99 @@ def _spatial_image_scale_factors(spatial_image, min_length):
3631

3732
return scale_factors
3833

39-
def _make_multiscale_store(multiscale):
34+
def _make_multiscale_store():
4035
# Todo: for very large images serialize to disk cache
36+
# -> create DirectoryStore in cache directory and return as the chunk_store
4137
store = zarr.storage.MemoryStore(dimension_separator='/')
42-
multiscale.to_zarr(store, compute=True)
43-
return store
38+
return store, None
4439

4540
def _get_viewer_image(image, label=False):
41+
# NGFF Zarr
42+
if isinstance(image, zarr.Group) and 'multiscales' in image.attrs:
43+
return image.store
44+
4645
min_length = 64
4746
if label:
4847
method = Methods.DASK_IMAGE_NEAREST
4948
else:
5049
method = Methods.DASK_IMAGE_GAUSSIAN
51-
if isinstance(image, MultiscaleSpatialImage):
52-
return _make_multiscale_store(image)
50+
51+
store, chunk_store = _make_multiscale_store()
52+
53+
if HAVE_MULTISCALE_SPATIAL_IMAGE:
54+
if isinstance(image, MultiscaleSpatialImage):
55+
image.to_zarr(store, compute=True)
56+
return store
57+
58+
if isinstance(image, itkwasm.Image):
59+
ngff_image = itk_image_to_ngff_image(image)
60+
multiscales = to_multiscales(ngff_image, method=method)
61+
to_ngff_zarr(store, multiscales, chunk_store=chunk_store)
5362

54-
# Todo: support for itkwasm.Image
5563
if HAVE_ITK:
5664
import itk
5765
if isinstance(image, itk.Image):
58-
dimension = image.GetImageDimension()
59-
size = np.array(itk.size(image))
60-
scale_factors = []
61-
dims = ('x', 'y', 'z')
62-
previous = {'x': 1, 'y': 1, 'z': 1}
63-
while (size > min_length).any():
64-
to_skip = size <= size.max() / 2
65-
scale_factor = {}
66-
for dim in range(dimension):
67-
if to_skip[dim]:
68-
scale_factor[dims[dim]] = previous[dims[dim]]
69-
continue
70-
scale_factor[dims[dim]] = 2 * previous[dims[dim]]
71-
size[dim] = int(size[dim] / 2)
72-
previous = scale_factor
73-
scale_factors.append(scale_factor)
74-
75-
multiscale = itk_image_to_multiscale(image, scale_factors=scale_factors, method=method)
76-
return _make_multiscale_store(multiscale)
66+
ngff_image = itk_image_to_ngff_image(image)
67+
multiscales = to_multiscales(ngff_image, method=method)
68+
to_ngff_zarr(store, multiscales, chunk_store=chunk_store)
69+
return store
7770

7871
if HAVE_VTK:
7972
import vtk
8073
if isinstance(image, vtk.vtkImageData):
81-
spatial_image = vtk_image_to_spatial_image(image)
82-
scale_factors = _spatial_image_scale_factors(spatial_image, min_length)
83-
multiscale = to_multiscale(spatial_image, scale_factors, method=method)
84-
return _make_multiscale_store(multiscale)
74+
ngff_image = vtk_image_to_ngff_image(image)
75+
multiscales = to_multiscales(ngff_image, method=method)
76+
to_ngff_zarr(store, multiscales, chunk_store=chunk_store)
77+
return store
8578

8679
if isinstance(image, dask.array.core.Array):
87-
spatial_image = to_spatial_image(image)
88-
scale_factors = _spatial_image_scale_factors(spatial_image, min_length)
89-
multiscale = to_multiscale(spatial_image, scale_factors, method=method)
90-
return _make_multiscale_store(multiscale)
80+
ngff_image = to_ngff_image(image)
81+
multiscales = to_multiscales(ngff_image, method=method)
82+
to_ngff_zarr(store, multiscales, chunk_store=chunk_store)
83+
return store
9184

9285
if isinstance(image, zarr.Array):
93-
spatial_image = to_spatial_image(image)
94-
scale_factors = _spatial_image_scale_factors(spatial_image, min_length)
95-
multiscale = to_multiscale(spatial_image, scale_factors, method=method)
96-
return _make_multiscale_store(multiscale)
97-
98-
# NGFF Zarr
99-
if isinstance(image, zarr.Group) and 'multiscales' in image.attrs:
100-
return image.store
86+
ngff_image = to_ngff_image(image)
87+
multiscales = to_multiscales(ngff_image, method=method)
88+
to_ngff_zarr(store, multiscales, chunk_store=chunk_store)
89+
return store
10190

10291
if HAVE_TORCH:
10392
import torch
10493
if isinstance(image, torch.Tensor):
105-
spatial_image = to_spatial_image(image.numpy())
106-
scale_factors = _spatial_image_scale_factors(spatial_image, min_length)
107-
multiscale = to_multiscale(spatial_image, scale_factors, method=method)
108-
return _make_multiscale_store(multiscale)
94+
ngff_image = to_ngff_image(image.numpy())
95+
multiscales = to_multiscales(ngff_image, method=method)
96+
to_ngff_zarr(store, multiscales, chunk_store=chunk_store)
97+
return store
10998

11099
# Todo: preserve dask Array, if present, check if dims are NGFF -> use dims, coords
111100
# Check if coords are uniform, if not, resample
112-
if isinstance(image, xr.DataArray):
113-
if is_spatial_image(image):
114-
scale_factors = _spatial_image_scale_factors(image, min_length)
115-
multiscale = to_multiscale(image, scale_factors, method=method)
116-
return _make_multiscale_store(multiscale)
117-
118-
return xarray_data_array_to_numpy(image)
119-
if isinstance(image, xr.Dataset):
120-
da = image[next(iter(image.variables.keys()))]
121-
if is_spatial_image(da):
122-
scale_factors = _spatial_image_scale_factors(da, min_length)
123-
multiscale = to_multiscale(da, scale_factors, method=method)
124-
return _make_multiscale_store(multiscale)
125-
return xarray_data_set_to_numpy(image)
101+
if HAVE_XARRAY:
102+
import xarray as xr
103+
if isinstance(image, xr.DataArray):
104+
# if HAVE_MULTISCALE_SPATIAL_IMAGE:
105+
# from spatial_image import is_spatial_image
106+
# if is_spatial_image(image):
107+
# from multiscale_spatial_image import to_multiscale
108+
# scale_factors = _spatial_image_scale_factors(image, min_length)
109+
# multiscale = to_multiscale(image, scale_factors, method=method)
110+
# return _make_multiscale_store(multiscale)
111+
112+
return xarray_data_array_to_numpy(image)
113+
if isinstance(image, xr.Dataset):
114+
# da = image[next(iter(image.variables.keys()))]
115+
# if is_spatial_image(da):
116+
# scale_factors = _spatial_image_scale_factors(da, min_length)
117+
# multiscale = to_multiscale(da, scale_factors, method=method)
118+
# return _make_multiscale_store(multiscale)
119+
return xarray_data_set_to_numpy(image)
126120

127121
if isinstance(image, np.ndarray):
128-
spatial_image = to_spatial_image(image)
129-
scale_factors = _spatial_image_scale_factors(spatial_image, min_length)
130-
multiscale = to_multiscale(spatial_image, scale_factors, method=method)
131-
return _make_multiscale_store(multiscale)
122+
ngff_image = to_ngff_image(image)
123+
multiscales = to_multiscales(ngff_image, method=method)
124+
to_ngff_zarr(store, multiscales, chunk_store=chunk_store)
125+
return store
126+
132127
raise RuntimeError("Could not process the viewer image")
133128

134129

@@ -143,10 +138,12 @@ def _get_viewer_point_sets(point_sets):
143138
import torch
144139
if isinstance(point_sets, torch.Tensor):
145140
return point_sets.numpy()
146-
if isinstance(point_sets, xr.DataArray):
147-
return xarray_data_array_to_numpy(point_sets)
148-
if isinstance(point_sets, xr.Dataset):
149-
return xarray_data_set_to_numpy(point_sets)
141+
if HAVE_XARRAY:
142+
import xarray as xr
143+
if isinstance(point_sets, xr.DataArray):
144+
return xarray_data_array_to_numpy(point_sets)
145+
if isinstance(point_sets, xr.Dataset):
146+
return xarray_data_set_to_numpy(point_sets)
150147
return point_sets
151148

152149

@@ -155,8 +152,10 @@ def _detect_render_type(data, input_type) -> RenderType:
155152
return RenderType.IMAGE
156153
elif isinstance(data, itkwasm.PointSet):
157154
return RenderType.POINT_SET
158-
elif isinstance(data, MultiscaleSpatialImage):
159-
return RenderType.IMAGE
155+
elif HAVE_MULTISCALE_SPATIAL_IMAGE:
156+
from multiscale_spatial_image import MultiscaleSpatialImage
157+
if isinstance(data, MultiscaleSpatialImage):
158+
return RenderType.IMAGE
160159
elif isinstance(data, (zarr.Array, zarr.Group)):
161160
# For now assume zarr.Group is an image
162161
# In the future, once NGFF supports point sets fully
@@ -194,11 +193,13 @@ def _detect_render_type(data, input_type) -> RenderType:
194193
return RenderType.POINT_SET
195194
else:
196195
return RenderType.IMAGE
197-
if isinstance(data, xr.DataArray):
198-
if input_type == 'point_sets':
199-
return RenderType.POINT_SET
200-
else:
201-
return RenderType.IMAGE
196+
if HAVE_XARRAY:
197+
import xarray as xr
198+
if isinstance(data, xr.DataArray):
199+
if input_type == 'point_sets':
200+
return RenderType.POINT_SET
201+
else:
202+
return RenderType.IMAGE
202203
if isinstance(data, xr.Dataset):
203204
if input_type == 'point_sets':
204205
return RenderType.POINT_SET

itkwidgets/integrations/itk.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,11 @@
1212

1313

1414
if HAVE_ITK:
15-
def itk_image_to_wasm_image(image):
16-
image_dict = itk.dict_from_image(image)
17-
wasm_image = itkwasm.Image(**image_dict)
18-
return wasm_image
19-
2015
def itk_group_spatial_object_to_wasm_point_set(point_set):
2116
point_set_dict = itk.dict_from_pointset(point_set)
2217
wasm_point_set = itkwasm.PointSet(**point_set_dict)
2318
return wasm_point_set
2419

2520
else:
26-
def itk_image_to_wasm_image(image):
27-
raise RuntimeError('itk 5.3rc4 or newer is required. `pip install --upgrade --pre itk`')
28-
2921
def itk_group_spatial_object_to_wasm_point_set(point_set):
3022
raise RuntimeError('itk 5.3rc4 or newer is required. `pip install --upgrade --pre itk`')

itkwidgets/integrations/vtk.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
except ImportError:
77
pass
88

9-
from spatial_image import to_spatial_image
9+
from ngff_zarr import ngff_image, to_ngff_image
1010

1111

12-
def vtk_image_to_spatial_image(image):
12+
def vtk_image_to_ngff_image(image):
1313
array = vtk_to_numpy(image.GetPointData().GetScalars())
1414
dimensions = list(image.GetDimensions())
1515
array.shape = dimensions[::-1]
@@ -20,9 +20,9 @@ def vtk_image_to_spatial_image(image):
2020
spacing = image.GetSpacing()
2121
scale = { 'x': spacing[0], 'y': spacing[1], 'z': spacing[2] }
2222

23-
spatial_image = to_spatial_image(array, scale=scale, translation=translation)
23+
ngff_image = to_ngff_image(array, scale=scale, translation=translation)
2424

25-
return spatial_image
25+
return ngff_image
2626

2727
def vtk_polydata_to_vtkjs(point_set):
2828
array = vtk_to_numpy(point_set.GetPoints().GetData())

itkwidgets/integrations/xarray.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
except ImportError:
66
pass
77

8+
HAVE_MULTISCALE_SPATIAL_IMAGE = False
9+
try:
10+
import multiscale_spatial_image
11+
HAVE_MULTISCALE_SPATIAL_IMAGE = True
12+
except ImportError:
13+
pass
14+
815
def xarray_data_array_to_numpy(data_array):
916
return data_array.to_numpy()
1017

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ keywords = [
3333
"webgpu",
3434
]
3535

36-
requires-python = ">=3.8"
36+
requires-python = ">=3.7"
3737
dependencies = [
3838
"itkwasm >= 1.0b1",
3939
"imjoy-rpc >= 0.5.16",
4040
"imjoy-utils >= 0.1.2",
41+
"ngff-zarr[dask-image] >= 0.1.0",
4142
"numcodecs",
42-
"multiscale_spatial_image[dask-image] >= 0.11.1",
4343
"zarr",
4444
]
4545

0 commit comments

Comments
 (0)