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 authored and gwbischof committed Mar 5, 2024
1 parent acfa2a2 commit db72097
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 11 deletions.
14 changes: 14 additions & 0 deletions tiled/_tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,17 @@ def test_array_interface(context):
# 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]
63 changes: 52 additions & 11 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 @@ -150,14 +153,52 @@ def slice_(
"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
]
)
# 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("", regex=SLICE_REGEX),
):
"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 db72097

Please sign in to comment.