Skip to content

Commit 4fc3d11

Browse files
committed
Merge branch 'main' of https://github.com/JaneliaSciComp/xarray-multiscale into use-hatch
2 parents 486a8b7 + 3946b05 commit 4fc3d11

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

src/xarray_multiscale/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
windowed_max,
66
windowed_mean,
77
windowed_min,
8-
windowed_mode,
8+
windowed_rank,
99
)
1010

1111
__all__ = [
@@ -15,4 +15,5 @@
1515
"windowed_mode",
1616
"windowed_max",
1717
"windowed_min",
18+
"windowed_rank",
1819
]

src/xarray_multiscale/reducers.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,60 @@ def windowed_mode_countless(
378378
final_result: npt.NDArray[Any] = _lor(reduce(_lor, results), sections[-1]) - 1 # type: ignore[arg-type]
379379

380380
return final_result
381+
382+
383+
def windowed_rank(
384+
array: npt.NDArray[Any], window_size: tuple[int, ...], rank: int = -1
385+
) -> npt.NDArray[Any]:
386+
"""
387+
Compute the windowed rank order filter of an array.
388+
Input will be coerced to a numpy array.
389+
390+
Parameters
391+
----------
392+
array: Array-like, e.g. Numpy array, Dask array
393+
The array to be downscaled. The array must have a ``reshape``
394+
method.
395+
396+
window_size: tuple[int, ...]
397+
The window to use for aggregation. The array is partitioned into
398+
non-overlapping regions with size equal to ``window_size``, and the
399+
values in each window are sorted to generate the result.
400+
401+
rank: int, default=-1
402+
The index to take from the sorted values in each window. If non-negative, then
403+
rank must be between 0 and the product of the elements of ``window_size`` minus one,
404+
(inclusive).
405+
Rank may be negative, in which case it denotes an index relative to the end of the sorted
406+
values following normal python indexing rules.
407+
E.g., when rank is -1 (the default), this takes the maxmum value of each window.
408+
409+
Returns
410+
-------
411+
Numpy array
412+
The result of the windowed rank filter. The length of each axis of this array
413+
will be a fraction of the input.
414+
415+
Examples
416+
--------
417+
>>> import numpy as np
418+
>>> from xarray_multiscale.reducers import windowed_rank
419+
>>> data = np.arange(16).reshape(4, 4)
420+
>>> windowed_rank(data, (2, 2), -2)
421+
array([[ 4 6]
422+
[12 14]])
423+
"""
424+
max_rank = np.prod(window_size) - 1
425+
if rank > max_rank or rank < -max_rank - 1:
426+
msg = (
427+
f"Invalid rank: {rank} for window_size: {window_size} ",
428+
f"If rank is negative then between either -1 and {-max_rank-1}, inclusive",
429+
f"If rank is non-negtaive, then it must be between 0 and {max_rank}, inclusive.",
430+
)
431+
raise ValueError(msg)
432+
reshaped = reshape_windowed(array, window_size)
433+
transposed_shape = tuple(range(0, reshaped.ndim, 2)) + tuple(range(1, reshaped.ndim, 2))
434+
transposed = reshaped.transpose(transposed_shape)
435+
collapsed = transposed.reshape(tuple(reshaped.shape[slice(0, None, 2)]) + (-1,))
436+
result: npt.NDArray[Any] = np.take(np.sort(collapsed, axis=-1), rank, axis=-1)
437+
return result

tests/test_reducers.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
windowed_mean,
1111
windowed_min,
1212
windowed_mode,
13+
windowed_rank,
1314
)
1415

1516

@@ -62,6 +63,29 @@ def test_windowed_mode():
6263
assert np.array_equal(results, answer)
6364

6465

66+
def test_windowed_rank():
67+
initial_array = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
68+
larger_array = np.tile(initial_array, (2, 2, 2))
69+
window_size = (2, 2, 2)
70+
71+
# 2nd brightest voxel
72+
rank = np.product(window_size) - 2
73+
answer = np.array([[[7, 7], [7, 7]], [[7, 7], [7, 7]]])
74+
results = windowed_rank(larger_array, window_size, rank)
75+
assert np.array_equal(results, answer)
76+
77+
# Test negative rank
78+
rank = -8
79+
answer = np.array([[[1, 1], [1, 1]], [[1, 1], [1, 1]]])
80+
results = windowed_rank(larger_array, window_size, rank)
81+
assert np.array_equal(results, answer)
82+
83+
# Test out-of-bounds rank
84+
rank = 100
85+
with pytest.raises(ValueError):
86+
windowed_rank(larger_array, window_size, rank)
87+
88+
6589
@pytest.mark.parametrize("windows_per_dim", (1, 2, 3, 4, 5))
6690
@pytest.mark.parametrize(
6791
"window_size", ((1,), (2,), (1, 2), (2, 2), (2, 2, 2), (1, 2, 3), (3, 3, 3, 3))

0 commit comments

Comments
 (0)