funlib.persistence 0.7.0__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/PKG-INFO +1 -1
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/__init__.py +1 -1
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/arrays/array.py +98 -18
- funlib_persistence-0.8.0/src/funlib/persistence/arrays/datasets.py +576 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/graphs/pgsql_graph_database.py +36 -6
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/graphs/sql_graph_database.py +27 -4
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/graphs/sqlite_graph_database.py +7 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib.persistence.egg-info/PKG-INFO +1 -1
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib.persistence.egg-info/SOURCES.txt +1 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/tests/test_array.py +54 -0
- funlib_persistence-0.8.0/tests/test_datasets.py +995 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/tests/test_graph.py +83 -0
- funlib_persistence-0.8.0/tests/test_pgsql_graph_database.py +91 -0
- funlib_persistence-0.7.0/src/funlib/persistence/arrays/datasets.py +0 -372
- funlib_persistence-0.7.0/tests/test_datasets.py +0 -292
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/LICENSE +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/README.md +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/pyproject.toml +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/setup.cfg +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/arrays/__init__.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/arrays/freezable.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/arrays/lazy_ops.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/arrays/metadata.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/arrays/ome_datasets.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/arrays/utils.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/graphs/__init__.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/graphs/graph_database.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/py.typed +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/types.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib.persistence.egg-info/dependency_links.txt +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib.persistence.egg-info/requires.txt +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib.persistence.egg-info/top_level.txt +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/tests/test_config.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/tests/test_metadata.py +0 -0
- {funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/tests/test_slices.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: funlib.persistence
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Interfaces for data (arrays and graphs) and storage formats (databases and file formats)
|
|
5
5
|
Author-email: William Patton <pattonw@hhmi.org>, Jan Funke <funkej@hhmi.org>
|
|
6
6
|
License: MIT
|
{funlib_persistence-0.7.0 → funlib_persistence-0.8.0}/src/funlib/persistence/arrays/array.py
RENAMED
|
@@ -74,6 +74,18 @@ class Array(Freezable):
|
|
|
74
74
|
offset, voxel_size, axis_names, units, and types. Metadata
|
|
75
75
|
can either be passed in or read from array attributes.
|
|
76
76
|
|
|
77
|
+
scheduler (``Optional[str]``):
|
|
78
|
+
|
|
79
|
+
The dask scheduler used to compute reads (`__getitem__` and
|
|
80
|
+
`to_ndarray`); writes do not go through dask. `None` (the default)
|
|
81
|
+
leaves the choice to dask, which uses its threaded scheduler for
|
|
82
|
+
arrays. Pass `"single-threaded"` when the caller already
|
|
83
|
+
parallelises array access (one process or thread per block, as
|
|
84
|
+
daisy does), where dask's own thread pool only adds contention
|
|
85
|
+
and can deadlock. See
|
|
86
|
+
https://docs.dask.org/en/stable/scheduler-overview.html for the
|
|
87
|
+
available schedulers.
|
|
88
|
+
|
|
77
89
|
"""
|
|
78
90
|
|
|
79
91
|
data: da.Array
|
|
@@ -89,7 +101,9 @@ class Array(Freezable):
|
|
|
89
101
|
chunks: Optional[Union[int, Sequence[int], str]] = "auto",
|
|
90
102
|
lazy_op: Optional[LazyOp] = None,
|
|
91
103
|
strict_metadata: bool = False,
|
|
104
|
+
scheduler: Optional[str] = None,
|
|
92
105
|
):
|
|
106
|
+
self.scheduler = scheduler
|
|
93
107
|
if not isinstance(data, da.Array):
|
|
94
108
|
self.data = da.from_array(data, chunks=chunks)
|
|
95
109
|
else:
|
|
@@ -110,11 +124,13 @@ class Array(Freezable):
|
|
|
110
124
|
# only used if not reading from zarr and there is no built in `.attrs`
|
|
111
125
|
self._attrs: dict[str, Any] = {}
|
|
112
126
|
|
|
127
|
+
# whether the view's chunks still sit on the stored chunk grid (see
|
|
128
|
+
# `_keeps_chunk_grid`); `chunk_shape` is reported only while they do
|
|
129
|
+
self._on_chunk_grid = True
|
|
130
|
+
self.lazy_ops: list[LazyOp] = []
|
|
113
131
|
if lazy_op is not None:
|
|
114
132
|
self.apply_lazy_ops(lazy_op)
|
|
115
|
-
|
|
116
|
-
lazy_ops = [] if lazy_op is None else [lazy_op]
|
|
117
|
-
self.lazy_ops = lazy_ops
|
|
133
|
+
self.lazy_ops = [lazy_op]
|
|
118
134
|
|
|
119
135
|
self.freeze()
|
|
120
136
|
|
|
@@ -133,9 +149,67 @@ class Array(Freezable):
|
|
|
133
149
|
return self._attrs
|
|
134
150
|
|
|
135
151
|
@property
|
|
136
|
-
def chunk_shape(self) -> Coordinate:
|
|
152
|
+
def chunk_shape(self) -> Optional[Coordinate]:
|
|
153
|
+
"""The write-safe chunk shape: the shard shape if sharded, else the chunk
|
|
154
|
+
shape (an ``open_ds(chunks=...)`` override is reported as given). ``None``
|
|
155
|
+
for a view that is not writeable or that lazy operations moved off the
|
|
156
|
+
stored chunk grid."""
|
|
157
|
+
if not self.is_writeable or not self._on_chunk_grid:
|
|
158
|
+
return None
|
|
159
|
+
shards = self.shard_shape
|
|
160
|
+
return shards if shards is not None else self._dask_chunk_shape()
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def inner_chunk_shape(self) -> Optional[Coordinate]:
|
|
164
|
+
"""Chunk shape for efficient reads. Can be smaller than the write-safe
|
|
165
|
+
:attr:`chunk_shape` in the case of sharding. ``None`` for a view that
|
|
166
|
+
lazy operations moved off the stored chunk grid."""
|
|
167
|
+
return self._dask_chunk_shape() if self._on_chunk_grid else None
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def shard_shape(self) -> Optional[Coordinate]:
|
|
171
|
+
"""The shard shape in the case of sharded datasets. ``None`` for non
|
|
172
|
+
sharded or unwritable datasets, or off the stored chunk grid. See
|
|
173
|
+
https://zarr.readthedocs.io/en/stable/user-guide/performance.html#sharding
|
|
174
|
+
"""
|
|
175
|
+
shards = getattr(self._source_data, "shards", None)
|
|
176
|
+
if shards is None or not self.is_writeable or not self._on_chunk_grid:
|
|
177
|
+
return None
|
|
178
|
+
return Coordinate(
|
|
179
|
+
s for s, uncollapsed in zip(shards, self._uncollapsed_dims) if uncollapsed
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _dask_chunk_shape(self) -> Coordinate:
|
|
137
183
|
return Coordinate(self.data.chunksize) # ty: ignore[unresolved-attribute]
|
|
138
184
|
|
|
185
|
+
def _keeps_chunk_grid(self, lazy_op) -> bool:
|
|
186
|
+
"""Whether a lazy op leaves this view's chunks on the stored chunk grid:
|
|
187
|
+
dropping or truncating axes does, a start, a step, a selection list, a
|
|
188
|
+
mask or an arbitrary callable does not. A ``Roi`` does if it begins on
|
|
189
|
+
a chunk boundary."""
|
|
190
|
+
if isinstance(lazy_op, Roi):
|
|
191
|
+
if (
|
|
192
|
+
not all(self.uncollapsed_dims(physical=True))
|
|
193
|
+
or lazy_op.dims != self.spatial_dims
|
|
194
|
+
):
|
|
195
|
+
return True # apply_lazy_ops refuses the op, with its own message
|
|
196
|
+
unit = self.chunk_shape
|
|
197
|
+
if unit is None:
|
|
198
|
+
return False
|
|
199
|
+
physical = [c for c, t in zip(unit, self.types) if t in ["space", "time"]]
|
|
200
|
+
begin = (lazy_op.offset - self.offset) / self.voxel_size
|
|
201
|
+
return all(b % c == 0 for b, c in zip(begin, physical))
|
|
202
|
+
if not isinstance(lazy_op, tuple):
|
|
203
|
+
lazy_op = (lazy_op,)
|
|
204
|
+
for a in lazy_op:
|
|
205
|
+
if isinstance(a, int):
|
|
206
|
+
continue
|
|
207
|
+
if isinstance(a, slice):
|
|
208
|
+
if a.start in (None, 0) and a.step in (None, 1):
|
|
209
|
+
continue
|
|
210
|
+
return False
|
|
211
|
+
return True
|
|
212
|
+
|
|
139
213
|
def uncollapsed_dims(self, physical: bool = False) -> list[bool]:
|
|
140
214
|
"""
|
|
141
215
|
We support lazy slicing of arrays, such as `x = Array(np.ones(5,5,5))` and
|
|
@@ -253,17 +327,18 @@ class Array(Freezable):
|
|
|
253
327
|
)
|
|
254
328
|
|
|
255
329
|
def apply_lazy_ops(self, lazy_op):
|
|
330
|
+
# decided before the op moves the offset, recorded only once it applied
|
|
331
|
+
on_chunk_grid = self._on_chunk_grid and self._keeps_chunk_grid(lazy_op)
|
|
256
332
|
if self._is_slice(lazy_op):
|
|
257
333
|
if not isinstance(lazy_op, tuple):
|
|
258
334
|
lazy_op = (lazy_op,)
|
|
259
|
-
|
|
335
|
+
# Pair each entry with the dimension it indexes BEFORE collapsing any: the
|
|
336
|
+
# previous in-place walk re-counted uncollapsed dims after each collapse,
|
|
337
|
+
# so a tuple with two or more ints collapsed the wrong dimensions.
|
|
338
|
+
dims = [i for i, uc in enumerate(self._uncollapsed_dims) if uc]
|
|
339
|
+
for a, i in zip(lazy_op, dims):
|
|
260
340
|
if isinstance(a, int):
|
|
261
|
-
|
|
262
|
-
if uc:
|
|
263
|
-
if ii == 0:
|
|
264
|
-
self._uncollapsed_dims[i] = False
|
|
265
|
-
break
|
|
266
|
-
ii -= 1
|
|
341
|
+
self._uncollapsed_dims[i] = False
|
|
267
342
|
self.data = self.data[lazy_op]
|
|
268
343
|
elif isinstance(lazy_op, Roi):
|
|
269
344
|
assert all(self.uncollapsed_dims(physical=True)), (
|
|
@@ -282,6 +357,7 @@ class Array(Freezable):
|
|
|
282
357
|
f"LazyOp {lazy_op} is not a supported lazy_op. "
|
|
283
358
|
f"Supported lazy_ops are: {LazyOp}"
|
|
284
359
|
)
|
|
360
|
+
self._on_chunk_grid = on_chunk_grid
|
|
285
361
|
|
|
286
362
|
def lazy_op(self, lazy_op: LazyOp):
|
|
287
363
|
"""Apply an lazy operation to this array.
|
|
@@ -329,7 +405,9 @@ class Array(Freezable):
|
|
|
329
405
|
% (roi, self.roi)
|
|
330
406
|
)
|
|
331
407
|
|
|
332
|
-
return self.data[self.__slices(roi, use_lazy_slices=False)].compute(
|
|
408
|
+
return self.data[self.__slices(roi, use_lazy_slices=False)].compute(
|
|
409
|
+
scheduler=self.scheduler
|
|
410
|
+
)
|
|
333
411
|
|
|
334
412
|
elif isinstance(key, Coordinate):
|
|
335
413
|
coordinate = key
|
|
@@ -338,10 +416,10 @@ class Array(Freezable):
|
|
|
338
416
|
raise IndexError("Requested coordinate is not contained in this array.")
|
|
339
417
|
|
|
340
418
|
index = self.__index(coordinate)
|
|
341
|
-
return self.data[index].compute()
|
|
419
|
+
return self.data[index].compute(scheduler=self.scheduler)
|
|
342
420
|
|
|
343
421
|
else:
|
|
344
|
-
return self.data[key].compute()
|
|
422
|
+
return self.data[key].compute(scheduler=self.scheduler)
|
|
345
423
|
|
|
346
424
|
def __setitem__(self, key: Roi | slice | tuple, value: np.ndarray | float | int):
|
|
347
425
|
"""Set the data of this array.
|
|
@@ -421,7 +499,7 @@ class Array(Freezable):
|
|
|
421
499
|
if fill_value != 0:
|
|
422
500
|
data[:] = fill_value
|
|
423
501
|
|
|
424
|
-
array = Array(data, roi.offset, self.voxel_size)
|
|
502
|
+
array = Array(data, roi.offset, self.voxel_size, scheduler=self.scheduler)
|
|
425
503
|
|
|
426
504
|
shared_roi = self.roi.intersect(roi)
|
|
427
505
|
|
|
@@ -438,18 +516,20 @@ class Array(Freezable):
|
|
|
438
516
|
voxel_roi = (roi - self.offset) / self.voxel_size
|
|
439
517
|
|
|
440
518
|
if check_chunk_align:
|
|
519
|
+
chunk_shape = self.chunk_shape
|
|
520
|
+
assert chunk_shape is not None, "this view has no chunk grid to align to"
|
|
441
521
|
for d in range(roi.dims):
|
|
442
522
|
end_of_array = roi.get_end()[d] == self.roi.get_end()[d]
|
|
443
523
|
|
|
444
|
-
begin_align_with_chunks = voxel_roi.begin[d] %
|
|
445
|
-
shape_align_with_chunks = voxel_roi.shape[d] %
|
|
524
|
+
begin_align_with_chunks = voxel_roi.begin[d] % chunk_shape[d] == 0
|
|
525
|
+
shape_align_with_chunks = voxel_roi.shape[d] % chunk_shape[d] == 0
|
|
446
526
|
|
|
447
527
|
assert begin_align_with_chunks and (
|
|
448
528
|
shape_align_with_chunks or end_of_array
|
|
449
529
|
), (
|
|
450
530
|
"ROI %s (in voxels: %s) does not align with chunks of "
|
|
451
531
|
"size %s (mismatch in dimension %d)"
|
|
452
|
-
% (roi, voxel_roi,
|
|
532
|
+
% (roi, voxel_roi, chunk_shape, d)
|
|
453
533
|
)
|
|
454
534
|
|
|
455
535
|
roi_slices = tuple(
|