Skip to content

Commit 228e83f

Browse files
committed
ellipsis handling; resolves #93
1 parent 4baa306 commit 228e83f

File tree

3 files changed

+116
-30
lines changed

3 files changed

+116
-30
lines changed

zarr/tests/test_core.py

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ def test_array_1d(self):
123123
assert_array_equal(a[:10], z[:10])
124124
assert_array_equal(a[10:20], z[10:20])
125125
assert_array_equal(a[-10:], z[-10:])
126+
assert_array_equal(a[:10, ...], z[:10, ...])
127+
assert_array_equal(a[10:20, ...], z[10:20, ...])
128+
assert_array_equal(a[-10:, ...], z[-10:, ...])
129+
assert_array_equal(a[..., :10], z[..., :10])
130+
assert_array_equal(a[..., 10:20], z[..., 10:20])
131+
assert_array_equal(a[..., -10:], z[..., -10:])
126132
# ...across chunk boundaries...
127133
assert_array_equal(a[:110], z[:110])
128134
assert_array_equal(a[190:310], z[190:310])
@@ -135,6 +141,18 @@ def test_array_1d(self):
135141
eq(a[42], z[np.int32(42)])
136142
eq(a[42], z[np.uint64(42)])
137143
eq(a[42], z[np.uint32(42)])
144+
# too many indices
145+
with assert_raises(IndexError):
146+
z[:, :]
147+
with assert_raises(IndexError):
148+
z[0, :]
149+
with assert_raises(IndexError):
150+
z[:, 0]
151+
with assert_raises(IndexError):
152+
z[0, 0]
153+
# only single ellipsis allowed
154+
with assert_raises(IndexError):
155+
z[..., ...]
138156

139157
# check partial assignment
140158
b = np.arange(1e5, 2e5)
@@ -194,37 +212,84 @@ def test_array_2d(self):
194212
eq(a.nbytes, z.nbytes)
195213
eq(50, z.nchunks_initialized)
196214

197-
# check slicing
215+
# check array-like
198216
assert_array_equal(a, np.array(z))
217+
218+
# check slicing
219+
220+
# total slice
199221
assert_array_equal(a, z[:])
200222
assert_array_equal(a, z[...])
201223
# noinspection PyTypeChecker
202224
assert_array_equal(a, z[slice(None)])
225+
226+
# slice first dimension
203227
assert_array_equal(a[:10], z[:10])
204228
assert_array_equal(a[10:20], z[10:20])
205229
assert_array_equal(a[-10:], z[-10:])
230+
assert_array_equal(a[:10, :], z[:10, :])
231+
assert_array_equal(a[10:20, :], z[10:20, :])
232+
assert_array_equal(a[-10:, :], z[-10:, :])
233+
assert_array_equal(a[:10, ...], z[:10, ...])
234+
assert_array_equal(a[10:20, ...], z[10:20, ...])
235+
assert_array_equal(a[-10:, ...], z[-10:, ...])
236+
assert_array_equal(a[:10, :, ...], z[:10, :, ...])
237+
assert_array_equal(a[10:20, :, ...], z[10:20, :, ...])
238+
assert_array_equal(a[-10:, :, ...], z[-10:, :, ...])
239+
240+
# slice second dimension
206241
assert_array_equal(a[:, :2], z[:, :2])
207242
assert_array_equal(a[:, 2:4], z[:, 2:4])
208243
assert_array_equal(a[:, -2:], z[:, -2:])
244+
assert_array_equal(a[..., :2], z[..., :2])
245+
assert_array_equal(a[..., 2:4], z[..., 2:4])
246+
assert_array_equal(a[..., -2:], z[..., -2:])
247+
assert_array_equal(a[:, ..., :2], z[:, ..., :2])
248+
assert_array_equal(a[:, ..., 2:4], z[:, ..., 2:4])
249+
assert_array_equal(a[:, ..., -2:], z[:, ..., -2:])
250+
251+
# slice both dimensions
209252
assert_array_equal(a[:10, :2], z[:10, :2])
210253
assert_array_equal(a[10:20, 2:4], z[10:20, 2:4])
211254
assert_array_equal(a[-10:, -2:], z[-10:, -2:])
212-
# ...across chunk boundaries...
255+
256+
# slicing across chunk boundaries
213257
assert_array_equal(a[:110], z[:110])
214258
assert_array_equal(a[190:310], z[190:310])
215259
assert_array_equal(a[-110:], z[-110:])
260+
assert_array_equal(a[:110, :], z[:110, :])
261+
assert_array_equal(a[190:310, :], z[190:310, :])
262+
assert_array_equal(a[-110:, :], z[-110:, :])
216263
assert_array_equal(a[:, :3], z[:, :3])
217264
assert_array_equal(a[:, 3:7], z[:, 3:7])
218265
assert_array_equal(a[:, -3:], z[:, -3:])
219266
assert_array_equal(a[:110, :3], z[:110, :3])
220267
assert_array_equal(a[190:310, 3:7], z[190:310, 3:7])
221268
assert_array_equal(a[-110:, -3:], z[-110:, -3:])
222-
# single item
269+
270+
# single row/col/item
223271
assert_array_equal(a[0], z[0])
224272
assert_array_equal(a[-1], z[-1])
273+
assert_array_equal(a[:, 0], z[:, 0])
274+
assert_array_equal(a[:, -1], z[:, -1])
225275
eq(a[0, 0], z[0, 0])
226276
eq(a[-1, -1], z[-1, -1])
227277

278+
# too many indices
279+
with assert_raises(IndexError):
280+
z[:, :, :]
281+
with assert_raises(IndexError):
282+
z[0, :, :]
283+
with assert_raises(IndexError):
284+
z[:, 0, :]
285+
with assert_raises(IndexError):
286+
z[:, :, 0]
287+
with assert_raises(IndexError):
288+
z[0, 0, 0]
289+
# only single ellipsis allowed
290+
with assert_raises(IndexError):
291+
z[..., ...]
292+
228293
# check partial assignment
229294
b = np.arange(10000, 20000).reshape((1000, 10))
230295
z[190:310, 3:7] = b[190:310, 3:7]

zarr/tests/test_util.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,17 @@ def test_normalize_array_selection():
111111
eq((slice(0, 100),), normalize_array_selection(slice(None), (100,)))
112112
eq((slice(0, 100),), normalize_array_selection(slice(None, 100), (100,)))
113113
eq((slice(0, 100),), normalize_array_selection(slice(0, None), (100,)))
114+
eq((slice(0, 100),), normalize_array_selection((slice(None), Ellipsis), (100,)))
115+
eq((slice(0, 100),), normalize_array_selection((Ellipsis, slice(None)), (100,)))
114116

115117
# 2D, single item
116118
eq((0, 0), normalize_array_selection((0, 0), (100, 100)))
117119
eq((99, 1), normalize_array_selection((-1, 1), (100, 100)))
118120

119121
# 2D, single col/row
120-
eq((0, slice(0, 100)), normalize_array_selection((0, slice(None)),
121-
(100, 100)))
122-
eq((0, slice(0, 100)), normalize_array_selection((0,),
123-
(100, 100)))
124-
eq((slice(0, 100), 0), normalize_array_selection((slice(None), 0),
125-
(100, 100)))
122+
eq((0, slice(0, 100)), normalize_array_selection((0, slice(None)), (100, 100)))
123+
eq((0, slice(0, 100)), normalize_array_selection((0,), (100, 100)))
124+
eq((slice(0, 100), 0), normalize_array_selection((slice(None), 0), (100, 100)))
126125

127126
# 2D slice
128127
eq((slice(0, 100), slice(0, 100)),
@@ -131,6 +130,16 @@ def test_normalize_array_selection():
131130
normalize_array_selection(slice(None), (100, 100)))
132131
eq((slice(0, 100), slice(0, 100)),
133132
normalize_array_selection((slice(None), slice(None)), (100, 100)))
133+
eq((slice(0, 100), slice(0, 100)),
134+
normalize_array_selection((Ellipsis, slice(None)), (100, 100)))
135+
eq((slice(0, 100), slice(0, 100)),
136+
normalize_array_selection((slice(None), Ellipsis), (100, 100)))
137+
eq((slice(0, 100), slice(0, 100)),
138+
normalize_array_selection((slice(None), Ellipsis, slice(None)), (100, 100)))
139+
eq((slice(0, 100), slice(0, 100)),
140+
normalize_array_selection((Ellipsis, slice(None), slice(None)), (100, 100)))
141+
eq((slice(0, 100), slice(0, 100)),
142+
normalize_array_selection((slice(None), slice(None), Ellipsis), (100, 100)))
134143

135144
with assert_raises(TypeError):
136145
normalize_array_selection('foo', (100,))

zarr/util.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -192,29 +192,41 @@ def normalize_array_selection(item, shape):
192192
"""Convenience function to normalize a selection within an array with
193193
the given `shape`."""
194194

195-
# normalize item
196-
if isinstance(item, numbers.Integral):
197-
item = (int(item),)
198-
elif isinstance(item, slice):
195+
# ensure tuple
196+
if not isinstance(item, tuple):
199197
item = (item,)
200-
elif item == Ellipsis:
201-
item = (slice(None),)
202-
203-
# handle tuple of indices/slices
204-
if isinstance(item, tuple):
205198

206-
# determine start and stop indices for all axes
207-
selection = tuple(normalize_axis_selection(i, l)
208-
for i, l in zip(item, shape))
209-
210-
# fill out selection if not completely specified
211-
if len(selection) < len(shape):
212-
selection += tuple(slice(0, l) for l in shape[len(selection):])
213-
214-
return selection
215-
216-
else:
217-
raise TypeError('expected indices or slice, found: %r' % item)
199+
# handle ellipsis
200+
n_ellipsis = sum(1 for i in item if i == Ellipsis)
201+
if n_ellipsis > 1:
202+
raise IndexError("an index can only have a single ellipsis ('...')")
203+
elif n_ellipsis == 1:
204+
idx_ellipsis = item.index(Ellipsis)
205+
n_items_l = idx_ellipsis # items to left of ellipsis
206+
n_items_r = len(item) - (idx_ellipsis + 1) # items to right of ellipsis
207+
n_items = len(item) - 1 # all non-ellipsis items
208+
if n_items >= len(shape):
209+
# ellipsis does nothing, just remove it
210+
item = tuple(i for i in item if i != Ellipsis)
211+
else:
212+
# replace ellipsis with slices
213+
new_item = item[:n_items_l] + ((slice(None),) * (len(shape) - n_items))
214+
if n_items_r:
215+
new_item += item[-n_items_r:]
216+
item = new_item
217+
218+
# check dimensionality
219+
if len(item) > len(shape):
220+
raise IndexError('too many indices for array')
221+
222+
# determine start and stop indices for all axes
223+
selection = tuple(normalize_axis_selection(i, l) for i, l in zip(item, shape))
224+
225+
# fill out selection if not completely specified
226+
if len(selection) < len(shape):
227+
selection += tuple(slice(0, l) for l in shape[len(selection):])
228+
229+
return selection
218230

219231

220232
def get_chunk_range(selection, chunks):

0 commit comments

Comments
 (0)