Skip to content

Commit c47c68c

Browse files
authored
Merge pull request #101 from melonora/decorator
Add decorator for skipping nodes without dimension
2 parents f3f6c03 + 5faadba commit c47c68c

4 files changed

Lines changed: 101 additions & 0 deletions

File tree

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,58 @@ DataTree('multiscales', parent=None)
8383
image (y, x) uint8 dask.array<chunksize=(16, 16), meta=np.ndarray>
8484
```
8585

86+
Map a function over datasets while skipping nodes that do not contain dimensions
87+
88+
```python
89+
import numpy as np
90+
from spatial_image import to_spatial_image
91+
from multiscale_spatial_image import skip_non_dimension_nodes, to_multiscale
92+
93+
data = np.zeros((2, 200, 200))
94+
dims = ("c", "y", "x")
95+
scale_factors = [2, 2]
96+
image = to_spatial_image(array_like=data, dims=dims)
97+
multiscale = to_multiscale(image, scale_factors=scale_factors)
98+
99+
@skip_non_dimension_nodes
100+
def transpose(ds, *args, **kwargs):
101+
return ds.transpose(*args, **kwargs)
102+
103+
multiscale = multiscale.map_over_datasets(transpose, "y", "x", "c")
104+
print(multiscale)
105+
```
106+
107+
A transposed MultiscaleSpatialImage.
108+
109+
```
110+
<xarray.DataTree>
111+
Group: /
112+
├── Group: /scale0
113+
│ Dimensions: (c: 2, y: 200, x: 200)
114+
│ Coordinates:
115+
│ * c (c) int32 8B 0 1
116+
│ * y (y) float64 2kB 0.0 1.0 2.0 3.0 4.0 ... 196.0 197.0 198.0 199.0
117+
│ * x (x) float64 2kB 0.0 1.0 2.0 3.0 4.0 ... 196.0 197.0 198.0 199.0
118+
│ Data variables:
119+
│ image (y, x, c) float64 640kB dask.array<chunksize=(200, 200, 2), meta=np.ndarray>
120+
├── Group: /scale1
121+
│ Dimensions: (c: 2, y: 100, x: 100)
122+
│ Coordinates:
123+
│ * c (c) int32 8B 0 1
124+
│ * y (y) float64 800B 0.5 2.5 4.5 6.5 8.5 ... 192.5 194.5 196.5 198.5
125+
│ * x (x) float64 800B 0.5 2.5 4.5 6.5 8.5 ... 192.5 194.5 196.5 198.5
126+
│ Data variables:
127+
│ image (y, x, c) float64 160kB dask.array<chunksize=(100, 100, 2), meta=np.ndarray>
128+
└── Group: /scale2
129+
Dimensions: (c: 2, y: 50, x: 50)
130+
Coordinates:
131+
* c (c) int32 8B 0 1
132+
* y (y) float64 400B 1.5 5.5 9.5 13.5 17.5 ... 185.5 189.5 193.5 197.5
133+
* x (x) float64 400B 1.5 5.5 9.5 13.5 17.5 ... 185.5 189.5 193.5 197.5
134+
Data variables:
135+
image (y, x, c) float64 40kB dask.array<chunksize=(50, 50, 2), meta=np.ndarray>
136+
```
137+
86138
Store as an Open Microscopy Environment-Next Generation File Format ([OME-NGFF])
87139
/ [netCDF] [Zarr] store.
88140

multiscale_spatial_image/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
"Methods",
88
"to_multiscale",
99
"itk_image_to_multiscale",
10+
"skip_non_dimension_nodes",
1011
"__version__",
1112
]
1213

1314
from .__about__ import __version__
1415
from .multiscale_spatial_image import MultiscaleSpatialImage
1516
from .to_multiscale import Methods, to_multiscale, itk_image_to_multiscale
17+
from .utils import skip_non_dimension_nodes

multiscale_spatial_image/utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import Callable, Any
2+
from xarray import Dataset
3+
import functools
4+
5+
6+
def skip_non_dimension_nodes(
7+
func: Callable[[Dataset], Dataset],
8+
) -> Callable[[Dataset], Dataset]:
9+
"""Skip nodes in Datatree that do not contain dimensions.
10+
11+
This function implements the workaround of https://github.com/pydata/xarray/issues/9693. In particular,
12+
we need this because of our DataTree representing multiscale image having a root node that does not have
13+
dimensions. Several functions need to be mapped over the datasets in the datatree that depend on having
14+
dimensions, e.g. a transpose.
15+
"""
16+
17+
@functools.wraps(func)
18+
def _func(ds: Dataset, *args: Any, **kwargs: Any) -> Dataset:
19+
# check if dimensions are present otherwise return verbatim
20+
if len(ds.dims) == 0:
21+
return ds
22+
return func(ds, *args, **kwargs)
23+
24+
return _func

test/test_utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import numpy as np
2+
from spatial_image import to_spatial_image
3+
from multiscale_spatial_image import skip_non_dimension_nodes, to_multiscale
4+
5+
6+
def test_skip_nodes():
7+
data = np.zeros((2, 200, 200))
8+
dims = ("c", "y", "x")
9+
scale_factors = [2, 2]
10+
image = to_spatial_image(array_like=data, dims=dims)
11+
multiscale_img = to_multiscale(image, scale_factors=scale_factors)
12+
13+
@skip_non_dimension_nodes
14+
def transpose(ds, *args, **kwargs):
15+
return ds.transpose(*args, **kwargs)
16+
17+
for scale in list(multiscale_img.keys()):
18+
assert multiscale_img[scale]["image"].dims == ("c", "y", "x")
19+
20+
# applying this function without skipping the root node would fail as the root node does not have dimensions.
21+
result = multiscale_img.map_over_datasets(transpose, "y", "x", "c")
22+
for scale in list(result.keys()):
23+
assert result[scale]["image"].dims == ("y", "x", "c")

0 commit comments

Comments
 (0)