1+ from collections .abc import Sequence
12from typing import Any
23
34import numpy as np
89# See https://github.com/toloudis/ome-zarr-py/pull/1
910
1011
12+ def _better_chunksize (
13+ image : da .Array , factors : np .ndarray
14+ ) -> tuple [Sequence [int ], Sequence [int ]]:
15+ better_chunksize = tuple (
16+ np .maximum (1 , np .round (np .array (image .chunksize ) * factors ) / factors ).astype (
17+ int
18+ )
19+ )
20+
21+ # If E.g. we resize image from 6675 by 0.5 to 3337, factor is 0.49992509 so each
22+ # chunk of size e.g. 1000 will resize to 499. When assumbled into a new array, the
23+ # array will now be of size 3331 instead of 3337 because each of 6 chunks was
24+ # smaller by 1. When we compute() this, dask will read 6 chunks of 1000 and expect
25+ # last chunk to be 337 but instead it will only be 331.
26+ # So we use ceil() here (and in resize_block) to round 499.925 up to chunk of 500
27+ block_output_shape = tuple (
28+ np .ceil (np .array (better_chunksize ) * factors ).astype (int )
29+ )
30+
31+ return better_chunksize , block_output_shape
32+
33+
1134def resize (
12- image : da .Array , output_shape : tuple [int , ... ], * args : Any , ** kwargs : Any
35+ image : da .Array , output_shape : Sequence [int ], * args : Any , ** kwargs : Any
1336) -> da .Array :
1437 r"""
1538 Wrapped copy of "skimage.transform.resize"
1639 Resize image to match a certain size.
1740 :type image: :class:`dask.array`
1841 :param image: The dask array to resize
19- :type output_shape: tuple
42+ :type output_shape: Sequence[int]
2043 :param output_shape: The shape of the resize array
2144 :type \*args: list
2245 :param \*args: Arguments of skimage.transform.resize
@@ -27,23 +50,9 @@ def resize(
2750 factors = np .array (output_shape ) / np .array (image .shape ).astype (float )
2851 # Rechunk the input blocks so that the factors achieve an output
2952 # blocks size of full numbers.
30- better_chunksize = tuple (
31- np .maximum (1 , np .round (np .array (image .chunksize ) * factors ) / factors ).astype (
32- int
33- )
34- )
53+ better_chunksize , block_output_shape = _better_chunksize (image , factors )
3554 image_prepared = image .rechunk (better_chunksize )
3655
37- # If E.g. we resize image from 6675 by 0.5 to 3337, factor is 0.49992509 so each
38- # chunk of size e.g. 1000 will resize to 499. When assumbled into a new array, the
39- # array will now be of size 3331 instead of 3337 because each of 6 chunks was
40- # smaller by 1. When we compute() this, dask will read 6 chunks of 1000 and expect
41- # last chunk to be 337 but instead it will only be 331.
42- # So we use ceil() here (and in resize_block) to round 499.925 up to chunk of 500
43- block_output_shape = tuple (
44- np .ceil (np .array (better_chunksize ) * factors ).astype (int )
45- )
46-
4756 # Map overlap
4857 def resize_block (image_block : da .Array , block_info : dict ) -> da .Array :
4958 # if the input block is smaller than a 'regular' chunk (e.g. edge of image)
@@ -62,6 +71,65 @@ def resize_block(image_block: da.Array, block_info: dict) -> da.Array:
6271 return output .rechunk (image .chunksize ).astype (image .dtype )
6372
6473
74+ def local_mean (
75+ image : da .Array , output_shape : Sequence [int ], * args , ** kwargs
76+ ) -> da .Array :
77+ """
78+ Local mean downscaling.
79+
80+ :type image: :class:`dask.array.Array`
81+ :param image: The dask array to resize
82+ :type output_shape: Sequence[int]
83+ :param output_shape: The shape of the resize array
84+ :return: Resized image.
85+ """
86+ from skimage .transform import downscale_local_mean
87+
88+ factors = np .array (image .shape ).astype (float ) / np .array (output_shape )
89+ better_chunksize , block_output_shape = _better_chunksize (image , 1 / factors )
90+ image_prepared = image .rechunk (better_chunksize )
91+
92+ def local_mean_block (image_block : da .Array ) -> da .Array :
93+ local_mean = downscale_local_mean (
94+ image_block , tuple (factors .astype (int )), * args , ** kwargs
95+ )
96+ return local_mean .astype (image_block .dtype )
97+
98+ output_slices = tuple (slice (0 , d ) for d in output_shape )
99+ output = da .map_blocks (
100+ local_mean_block , image_prepared , dtype = image .dtype , chunks = block_output_shape
101+ )[output_slices ]
102+ return output .rechunk (image .chunksize ).astype (image .dtype )
103+
104+
105+ def zoom (image : da .Array , output_shape : Sequence [int ], * args , ** kwargs ) -> da .Array :
106+ """
107+ Scipy zoom downscaling by integer factors.
108+
109+ :type image: :class:`dask.array.Array`
110+ :param image: The dask array to resize
111+ :type output_shape: Sequence[int]
112+ :param output_shape: The shape of the resize array
113+ :return: Resized image.
114+ """
115+ from scipy .ndimage import zoom
116+
117+ factors = np .array (image .shape ).astype (float ) / np .array (output_shape )
118+ better_chunksize , block_output_shape = _better_chunksize (image , 1 / factors )
119+ image_prepared = image .rechunk (better_chunksize )
120+
121+ def zoom_block (image_block : da .Array ) -> da .Array :
122+ zoomed = zoom (image_block , 1 / factors , order = 1 )
123+ return zoomed .astype (image_block .dtype )
124+
125+ output_shape = tuple (d // f for d , f in zip (image .shape , factors ))
126+ output_slices = tuple (slice (0 , d ) for d in output_shape )
127+ output = da .map_blocks (
128+ zoom_block , image_prepared , dtype = image .dtype , chunks = block_output_shape
129+ )[output_slices ]
130+ return output .rechunk (image .chunksize ).astype (image .dtype )
131+
132+
65133def downscale_nearest (image : da .Array , factors : tuple [int , ...]) -> da .Array :
66134 """
67135 Primitive downscaling by integer factors using stepped slicing.
0 commit comments