Skip to content

Commit

Permalink
WIP: Support slices like '3:10:mean(2)'
Browse files Browse the repository at this point in the history
  • Loading branch information
danielballan committed Feb 8, 2023
1 parent 90fb121 commit f50389b
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 15 deletions.
14 changes: 14 additions & 0 deletions tiled/_tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,17 @@ def test_array_interface():
# smoke test
v.chunks
v.dims


def test_slices():
client = from_tree(cube_tree)
ac = client["tiny_cube"]
ac[:]
ac[:, :, :]
ac[..., 0, :1]
ac[1:4, -5:]
ac[-100:4, :-3]
ac[0, 3, 8]
ac[0, 3, :]
with pytest.raises(IndexError):
ac[100]
66 changes: 51 additions & 15 deletions tiled/server/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import builtins
import collections
import re
from functools import lru_cache
from typing import Optional

Expand Down Expand Up @@ -116,20 +119,53 @@ def expected_shape(
return tuple(map(int, expected_shape.split(",")))


# Accept numpy-style mutlidimesional slices and special constructs 'a:b:mean'
# and 'a:b:mean(c)' to represent downsampling, inspired by
# https://uhi.readthedocs.io/en/latest/
# Note: if you need to debug this, the interactive tool at https://regex101.com/ is your friend!
DIM_REGEX = r"(?:(-?[0-9]+)|(?:([0-9]*|-[0-9]+):(?:([0-9]*|-[0-9]+))?(?::(mean|mean\([0-9]+\)|[0-9]*|-[0-9]+))?))"
SLICE_REGEX = rf"^{DIM_REGEX}(,{DIM_REGEX})*$"
DIM_PATTERN = re.compile(rf"^{DIM_REGEX}$")
MEAN_PATTERN = re.compile(r"(mean|mean\(([0-9]+)\))")


# This object is meant to be placed at slice.step and used by the consumer to
# detect that it should agggregate, using
# numpy.mean or skimage.transform.downscale_local_mean.
Mean = collections.namedtuple("Mean", ["parameter"])


def _int_or_none(s):
return int(s) if s else None


def _mean_int_or_none(s):
if s is None:
return None
m = MEAN_PATTERN.match(s)
if m.group(0):
return Mean(m.group(1))
return _int_or_none(s)


def slice_(
slice: str = Query(None, regex="^[-0-9,:]*$"),
slice: str = Query("", regex=SLICE_REGEX),
):
"Specify and parse a block index parameter."
import numpy

# IMPORTANT We are eval-ing a user-provider string here so we need to be
# very careful about locking down what can be in it. The regex above
# excludes any letters or operators, so it is not possible to execute
# functions or expensive arithmetic.
return tuple(
[
eval(f"numpy.s_[{dim!s}]", {"numpy": numpy})
for dim in (slice or "").split(",")
if dim
]
)
"Specify and parse a slice parameter."
slices = []
for dim in slice.split(","):
if dim:
match = DIM_PATTERN.match(dim)
# Group 1 matches an int, as in arr[i].
if match.group(1) is not None:
s = int(match.group(1))
else:
# Groups 2 through 4 match a slice, as in arr[i:j:k].
s = builtins.slice(
_int_or_none(match.group(2)),
_int_or_none(match.group(3)),
_mean_int_or_none(match.group(4)),
)
slices.append(s)
print("slices", slices)
return tuple(slices)

0 comments on commit f50389b

Please sign in to comment.