ossify 0.2.0__tar.gz → 0.2.2__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.
- {ossify-0.2.0 → ossify-0.2.2}/PKG-INFO +2 -2
- {ossify-0.2.0 → ossify-0.2.2}/pyproject.toml +19 -5
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/__init__.py +1 -1
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/base.py +24 -1
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/data_layers.py +76 -13
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/translate.py +42 -25
- {ossify-0.2.0 → ossify-0.2.2}/.github/workflows/mkdocs_publish.yml +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/.github/workflows/python-package.yml +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/.gitignore +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/.pre-commit-config.yaml +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/LICENSE +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/README.md +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/mkdocs.yml +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/_sync/__init__.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/_sync/base.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/_sync/graph.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/_sync/mapping.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/_sync/mesh.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/_sync/morph.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/_sync/points.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/_sync/table.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/algorithms.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/compartment_models/minnie65_ds15_us0_bd0.json +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/compartment_models/v1dd_ds15_us0_bd0.json +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/compartment_models/xgb/minnie65_ds15_us0_bd0.ubj +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/compartment_models/xgb/v1dd_ds15_us0_bd0.ubj +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/compartments.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/file_io.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/graph_functions.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/plot.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/plot3d.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/plot_utils.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/structured_prediction.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/sync_classes.py +0 -0
- {ossify-0.2.0 → ossify-0.2.2}/src/ossify/utils.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ossify
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Mesh and skeleton analysis
|
|
5
5
|
Author-email: Casey Schneider-Mizell <caseysm@gmail.com>
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Classifier: License :: OSI Approved :: MIT License
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
|
-
Requires-Dist: caveclient>=
|
|
9
|
+
Requires-Dist: caveclient>=8.2.1
|
|
10
10
|
Requires-Dist: cloud-files>=5.8.0
|
|
11
11
|
Requires-Dist: fastremap>=1.15.1
|
|
12
12
|
Requires-Dist: matplotlib>=3.10.6
|
|
@@ -6,12 +6,12 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
allow-direct-references = true
|
|
7
7
|
[project]
|
|
8
8
|
name = "ossify"
|
|
9
|
-
version = "0.2.
|
|
9
|
+
version = "0.2.2"
|
|
10
10
|
description = "Mesh and skeleton analysis"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.11"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"caveclient>=
|
|
14
|
+
"caveclient>=8.2.1",
|
|
15
15
|
"numpy>=2.0.2",
|
|
16
16
|
"scipy>=1.15",
|
|
17
17
|
"trimesh>=4.6.2",
|
|
@@ -95,7 +95,7 @@ default-groups = ["dev", "docs", "lint", "profile", "viz"]
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
[tool.bumpversion]
|
|
98
|
-
current_version = "0.2.
|
|
98
|
+
current_version = "0.2.2"
|
|
99
99
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
100
100
|
serialize = ["{major}.{minor}.{patch}"]
|
|
101
101
|
regex = false
|
|
@@ -136,8 +136,22 @@ cmd = "uv run bump-my-version bump"
|
|
|
136
136
|
help = "Bump the version number in the project. Use with 'patch', 'minor', or 'major' to specify the version change."
|
|
137
137
|
|
|
138
138
|
[tool.poe.tasks.test]
|
|
139
|
-
cmd = "uv run pytest --cov=ossify tests"
|
|
140
|
-
help = "Run pytest with
|
|
139
|
+
cmd = "uv run pytest --cov=ossify --cov-branch --cov-report=term-missing tests"
|
|
140
|
+
help = "Run pytest with branch coverage and a missing-lines report."
|
|
141
|
+
|
|
142
|
+
[tool.pytest.ini_options]
|
|
143
|
+
# src-layout: put the package on the path so tests import ossify from src/
|
|
144
|
+
# during collection without relying on the editable install being present.
|
|
145
|
+
pythonpath = ["src"]
|
|
146
|
+
|
|
147
|
+
[tool.coverage.run]
|
|
148
|
+
branch = true
|
|
149
|
+
source = ["ossify"]
|
|
150
|
+
|
|
151
|
+
[tool.coverage.report]
|
|
152
|
+
# Surface partially-covered branches, not just uncovered lines.
|
|
153
|
+
show_missing = true
|
|
154
|
+
skip_covered = false
|
|
141
155
|
|
|
142
156
|
[tool.poe.tasks.doc-preview]
|
|
143
157
|
cmd = "uv run mkdocs serve"
|
|
@@ -848,6 +848,25 @@ class Cell:
|
|
|
848
848
|
)
|
|
849
849
|
return pd.concat(remap_features, axis=1)
|
|
850
850
|
|
|
851
|
+
def _close(self) -> None:
|
|
852
|
+
"""Release this Cell's data and break the layer<->cell reference cycle.
|
|
853
|
+
|
|
854
|
+
Layers hold a back-reference to their Cell (and vice versa), so a Cell
|
|
855
|
+
is only reclaimed by the cyclic garbage collector, not by refcounting.
|
|
856
|
+
``mask_context`` calls this on exit so the temporary masked copy is torn
|
|
857
|
+
down immediately -- freeing its (potentially large) data as soon as the
|
|
858
|
+
block ends rather than lingering until the next gc pass. The Cell must
|
|
859
|
+
not be used after it is closed.
|
|
860
|
+
"""
|
|
861
|
+
if self._morphsync is None:
|
|
862
|
+
return
|
|
863
|
+
for obj in list(self._all_objects.values()):
|
|
864
|
+
obj._cell = None
|
|
865
|
+
obj._morphsync = None
|
|
866
|
+
self._managed_layers.clear()
|
|
867
|
+
self._annotations._layers.clear()
|
|
868
|
+
self._morphsync = None
|
|
869
|
+
|
|
851
870
|
@contextlib.contextmanager
|
|
852
871
|
def mask_context(
|
|
853
872
|
self,
|
|
@@ -856,6 +875,10 @@ class Cell:
|
|
|
856
875
|
) -> Generator[Self, None, None]:
|
|
857
876
|
"""Create a masked version of the MeshWork object in a context state.
|
|
858
877
|
|
|
878
|
+
The masked Cell is a temporary scoped to the ``with`` block: it is torn
|
|
879
|
+
down when the block exits (including on error), so do not retain it or
|
|
880
|
+
any of its layers past the context.
|
|
881
|
+
|
|
859
882
|
Parameters
|
|
860
883
|
----------
|
|
861
884
|
layer: str
|
|
@@ -872,7 +895,7 @@ class Cell:
|
|
|
872
895
|
try:
|
|
873
896
|
yield nrn_out
|
|
874
897
|
finally:
|
|
875
|
-
|
|
898
|
+
nrn_out._close()
|
|
876
899
|
|
|
877
900
|
def _cleanup_links(self, layer_name: str) -> None:
|
|
878
901
|
"""Remove all links involving the specified layer from MorphSync."""
|
|
@@ -200,9 +200,11 @@ class EdgeMixin(ABC):
|
|
|
200
200
|
)
|
|
201
201
|
if limit is None:
|
|
202
202
|
limit = np.inf
|
|
203
|
+
# source_target_distances returns a (len(sources), len(targets)) matrix
|
|
204
|
+
# and indexes by these arrays, so scalar inputs must be promoted to 1-d.
|
|
203
205
|
return gf.source_target_distances(
|
|
204
|
-
sources=sources,
|
|
205
|
-
targets=targets,
|
|
206
|
+
sources=np.atleast_1d(sources),
|
|
207
|
+
targets=np.atleast_1d(targets),
|
|
206
208
|
csgraph=self.csgraph_undirected,
|
|
207
209
|
limit=limit,
|
|
208
210
|
)
|
|
@@ -305,6 +307,16 @@ class FaceMixin(ABC):
|
|
|
305
307
|
)
|
|
306
308
|
return self._csgraph
|
|
307
309
|
|
|
310
|
+
def _reset_derived_properties(self) -> None:
|
|
311
|
+
"""Reset cached properties derived from vertex positions.
|
|
312
|
+
|
|
313
|
+
The distance-weighted ``csgraph`` and the ``trimesh`` object both embed
|
|
314
|
+
vertex coordinates, so they must be dropped when the geometry changes
|
|
315
|
+
(e.g. after a spatial transform).
|
|
316
|
+
"""
|
|
317
|
+
self._csgraph = None
|
|
318
|
+
self._trimesh = None
|
|
319
|
+
|
|
308
320
|
def _map_faces_to_index(
|
|
309
321
|
self, faces: np.ndarray, vertex_indices: np.ndarray
|
|
310
322
|
) -> np.ndarray:
|
|
@@ -472,7 +484,12 @@ class PointMixin(ABC):
|
|
|
472
484
|
vertex_index_map = self.vertex_index_map
|
|
473
485
|
else:
|
|
474
486
|
vertex_index_map = {v: i for i, v in enumerate(vertex_index)}
|
|
475
|
-
|
|
487
|
+
# fastremap.remap rejects 0-d arrays, so a scalar vertex would crash
|
|
488
|
+
# here. Remap on a 1-d view and restore the caller's shape, keeping
|
|
489
|
+
# scalar-in -> scalar-out (as the positional path already does).
|
|
490
|
+
vertices = fastremap.remap(
|
|
491
|
+
np.atleast_1d(vertices), vertex_index_map
|
|
492
|
+
).reshape(vertices.shape)
|
|
476
493
|
vertices = np.array(vertices)
|
|
477
494
|
return vertices, as_positional
|
|
478
495
|
|
|
@@ -1090,9 +1107,10 @@ class PointMixin(ABC):
|
|
|
1090
1107
|
new_morphsync.layers.pop(l)
|
|
1091
1108
|
new_morphsync.links = {}
|
|
1092
1109
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
)
|
|
1110
|
+
# Copy this layer alone, detached from any Cell: build it from this
|
|
1111
|
+
# layer's own metadata (``_from_existing`` reads ``.nodes``/``.edges``/
|
|
1112
|
+
# etc.), not from ``self._cell`` (a Cell has no such layer attributes).
|
|
1113
|
+
return self.__class__._from_existing(new_morphsync=new_morphsync, old_obj=self)
|
|
1096
1114
|
|
|
1097
1115
|
def transform(
|
|
1098
1116
|
self, transform: Union[np.ndarray, Callable], inplace: bool = False
|
|
@@ -1111,8 +1129,22 @@ class PointMixin(ABC):
|
|
|
1111
1129
|
target.layer.nodes[target.spatial_columns] = transform(
|
|
1112
1130
|
target.layer.vertices
|
|
1113
1131
|
)
|
|
1132
|
+
# Moving vertices changes Euclidean distances, so any geometry-derived
|
|
1133
|
+
# caches (distance-weighted csgraph, trimesh, base graphs) are now stale.
|
|
1134
|
+
target._invalidate_spatial_caches()
|
|
1114
1135
|
return target
|
|
1115
1136
|
|
|
1137
|
+
def _invalidate_spatial_caches(self) -> None:
|
|
1138
|
+
"""Invalidate caches derived from vertex positions.
|
|
1139
|
+
|
|
1140
|
+
Called after a spatial transform. Distance-weighted graphs and mesh
|
|
1141
|
+
objects depend on coordinates and must be rebuilt. Layers without such
|
|
1142
|
+
caches (e.g. plain point clouds) do nothing.
|
|
1143
|
+
"""
|
|
1144
|
+
reset = getattr(self, "_reset_derived_properties", None)
|
|
1145
|
+
if reset is not None:
|
|
1146
|
+
reset()
|
|
1147
|
+
|
|
1116
1148
|
@contextlib.contextmanager
|
|
1117
1149
|
def mask_context(self, mask: np.ndarray) -> Generator[Self, None, None]:
|
|
1118
1150
|
"""Context manager to temporarily apply a mask via the current layer.
|
|
@@ -1136,7 +1168,7 @@ class PointMixin(ABC):
|
|
|
1136
1168
|
try:
|
|
1137
1169
|
yield new_self
|
|
1138
1170
|
finally:
|
|
1139
|
-
|
|
1171
|
+
new_self._close()
|
|
1140
1172
|
|
|
1141
1173
|
def _register_cell(self, mws: "Cell") -> None:
|
|
1142
1174
|
"""Register a Cell object with this layer.
|
|
@@ -1148,6 +1180,18 @@ class PointMixin(ABC):
|
|
|
1148
1180
|
"""
|
|
1149
1181
|
self._cell = mws
|
|
1150
1182
|
|
|
1183
|
+
def _close(self) -> None:
|
|
1184
|
+
"""Release this layer's data and detach it from any Cell.
|
|
1185
|
+
|
|
1186
|
+
Used by ``mask_context`` (when it yields a bare layer rather than a
|
|
1187
|
+
Cell) so the temporary masked copy is torn down when the block exits.
|
|
1188
|
+
The layer must not be used after it is closed. See ``Cell._close``.
|
|
1189
|
+
"""
|
|
1190
|
+
if self._morphsync is None:
|
|
1191
|
+
return
|
|
1192
|
+
self._cell = None
|
|
1193
|
+
self._morphsync = None
|
|
1194
|
+
|
|
1151
1195
|
def get_unmapped_vertices(
|
|
1152
1196
|
self,
|
|
1153
1197
|
target_layers: Optional[Union[str, List[str]]] = None,
|
|
@@ -2117,6 +2161,17 @@ class SkeletonLayer(GraphLayer):
|
|
|
2117
2161
|
super()._reset_derived_properties()
|
|
2118
2162
|
self._dag_cache = gf.DAGCache()
|
|
2119
2163
|
|
|
2164
|
+
def _invalidate_spatial_caches(self) -> None:
|
|
2165
|
+
# Drops the lazy csgraph/dag caches, then refresh the eagerly-stored
|
|
2166
|
+
# distance-weighted base graph from the transformed coordinates. The
|
|
2167
|
+
# base graph is a snapshot of the original geometry, so this is only
|
|
2168
|
+
# possible when unmasked -- a masked skeleton's base graph spans
|
|
2169
|
+
# vertices that are absent here and cannot be rebuilt. (The binary base
|
|
2170
|
+
# graph is topology-only and unaffected by a spatial transform.)
|
|
2171
|
+
super()._invalidate_spatial_caches()
|
|
2172
|
+
if np.array_equal(self.vertex_index, self.base_vertex_index):
|
|
2173
|
+
self._base_properties["base_csgraph"] = self.csgraph
|
|
2174
|
+
|
|
2120
2175
|
def _infer_root(self, root: Optional[int]) -> int:
|
|
2121
2176
|
"""Infer the root node from the graph structure or validate provided root.
|
|
2122
2177
|
|
|
@@ -2227,18 +2282,26 @@ class SkeletonLayer(GraphLayer):
|
|
|
2227
2282
|
Self
|
|
2228
2283
|
"""
|
|
2229
2284
|
self._reset_derived_properties()
|
|
2230
|
-
|
|
2231
|
-
|
|
2285
|
+
# ``_root``/``base_root`` and ``_apply_root_to_edges`` all work in vertex
|
|
2286
|
+
# index space, so normalize a positional argument up front rather than
|
|
2287
|
+
# converting to positional (which the rest of the class does not expect).
|
|
2288
|
+
if as_positional:
|
|
2289
|
+
new_root = int(self.vertex_index[new_root])
|
|
2232
2290
|
self._root = new_root
|
|
2233
|
-
self._dag_cache.root = self.
|
|
2234
|
-
self._apply_root_to_edges(new_root)
|
|
2291
|
+
self._dag_cache.root = self.root_positional
|
|
2292
|
+
self._dag_cache.parent_node_array = self._apply_root_to_edges(new_root)
|
|
2293
|
+
# Rebuild the full base-property snapshot from the rerooted skeleton.
|
|
2294
|
+
# This must include every key ``_set_base_properties`` normally writes --
|
|
2295
|
+
# notably ``base_csgraph_binary`` (used by ``hops_to_root``) -- because a
|
|
2296
|
+
# provided dict replaces ``_base_properties`` wholesale.
|
|
2235
2297
|
self._set_base_properties(
|
|
2236
2298
|
base_properties={
|
|
2237
|
-
"base_root":
|
|
2299
|
+
"base_root": self.root,
|
|
2300
|
+
"base_root_location": self.root_location,
|
|
2238
2301
|
"base_vertex_index": self.vertex_index,
|
|
2239
2302
|
"base_parent_array": self.parent_node_array,
|
|
2240
2303
|
"base_csgraph": self.csgraph,
|
|
2241
|
-
"
|
|
2304
|
+
"base_csgraph_binary": self.csgraph_binary,
|
|
2242
2305
|
}
|
|
2243
2306
|
)
|
|
2244
2307
|
return self
|
|
@@ -23,10 +23,10 @@ if TYPE_CHECKING:
|
|
|
23
23
|
from caveclient import CAVEclientFull as CAVEclient
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
#
|
|
27
|
-
# (caveclient.skeletonservice.
|
|
28
|
-
#
|
|
29
|
-
_BULK_SKELETON_CHUNK =
|
|
26
|
+
# Per-call cap for cached bulk-skeleton downloads via ``client.skeleton.fetch_skeletons``
|
|
27
|
+
# (caveclient.skeletonservice.MAX_BULK_CACHED_SKELETONS); longer lists are silently truncated
|
|
28
|
+
# by the endpoint, so callers must chunk at this size.
|
|
29
|
+
_BULK_SKELETON_CHUNK = 500
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class SWCCompartment(IntEnum):
|
|
@@ -563,23 +563,32 @@ def _get_bulk_skeletons(
|
|
|
563
563
|
client: "CAVEclient",
|
|
564
564
|
root_ids: list[int],
|
|
565
565
|
skeleton_version: int,
|
|
566
|
-
|
|
566
|
+
method: Literal["gcs", "server"] = "gcs",
|
|
567
567
|
) -> dict[int, dict]:
|
|
568
|
-
"""Download skeletons in bulk, chunked at the
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
568
|
+
"""Download cached skeletons in bulk, chunked at the cached-bulk cap.
|
|
569
|
+
|
|
570
|
+
Uses ``client.skeleton.fetch_skeletons``, which retrieves only already-cached skeletons and
|
|
571
|
+
skips the per-root chunkedgraph validation of the older ``get_bulk_skeletons`` path. With
|
|
572
|
+
``method="gcs"`` (the default) skeleton H5 files are downloaded directly from the storage
|
|
573
|
+
bucket via a short-lived downscoped token, bypassing the service for data transfer — this
|
|
574
|
+
avoids the request rate limits that throttle bulk loads through the server. ``method="server"``
|
|
575
|
+
routes the download through the skeleton service instead.
|
|
576
|
+
|
|
577
|
+
Skeletons are never generated inline (``generate_missing_skeletons=False`` always). Returns
|
|
578
|
+
``{int root_id: skeleton_dict}``; roots whose skeleton is not yet cached (async/not-generated)
|
|
579
|
+
are omitted — assuming a prior ``generate_bulk_skeletons_async`` pre-pass, these simply don't
|
|
580
|
+
appear.
|
|
573
581
|
"""
|
|
574
582
|
out: dict[int, dict] = {}
|
|
575
583
|
for i in range(0, len(root_ids), _BULK_SKELETON_CHUNK):
|
|
576
584
|
chunk = root_ids[i : i + _BULK_SKELETON_CHUNK]
|
|
577
585
|
with suppress_output():
|
|
578
|
-
res = client.skeleton.
|
|
586
|
+
res = client.skeleton.fetch_skeletons(
|
|
579
587
|
chunk,
|
|
580
588
|
skeleton_version=skeleton_version,
|
|
581
589
|
output_format="dict",
|
|
582
|
-
|
|
590
|
+
method=method,
|
|
591
|
+
generate_missing_skeletons=False,
|
|
583
592
|
)
|
|
584
593
|
for k, v in res.items():
|
|
585
594
|
out[int(k)] = v
|
|
@@ -601,7 +610,7 @@ def load_cell_batch_from_client(
|
|
|
601
610
|
omit_self_synapses: bool = True,
|
|
602
611
|
skeleton_version: int = 4,
|
|
603
612
|
skip_invalid: bool = False,
|
|
604
|
-
|
|
613
|
+
skeleton_download_method: Literal["gcs", "server"] = "gcs",
|
|
605
614
|
row_limit: int = 500_000,
|
|
606
615
|
) -> dict[int, Cell]:
|
|
607
616
|
"""Load many cells with the poolable fetches batched into a few queries.
|
|
@@ -611,6 +620,9 @@ def load_cell_batch_from_client(
|
|
|
611
620
|
each cell is then assembled network-free — no skeleton is fetched twice, and the batch is
|
|
612
621
|
validated with a single round trip. All roots share one ``timestamp``.
|
|
613
622
|
|
|
623
|
+
All parameters not listed below match :func:`load_cell_from_client` and are applied
|
|
624
|
+
identically to every cell in the batch.
|
|
625
|
+
|
|
614
626
|
Parameters
|
|
615
627
|
----------
|
|
616
628
|
root_ids: list[int]
|
|
@@ -622,11 +634,14 @@ def load_cell_batch_from_client(
|
|
|
622
634
|
skip_invalid: bool
|
|
623
635
|
If True, roots that are not valid at ``timestamp`` — or whose skeleton is not available
|
|
624
636
|
— are dropped from the result instead of raising.
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
and
|
|
629
|
-
|
|
637
|
+
skeleton_download_method: "gcs" or "server"
|
|
638
|
+
How ``fetch_skeletons`` retrieves cached skeletons. ``"gcs"`` (default) downloads H5 files
|
|
639
|
+
directly from the storage bucket via a downscoped token, bypassing the service for data
|
|
640
|
+
transfer and avoiding its request rate limits — preferred for bulk loads. ``"server"``
|
|
641
|
+
routes the download through the skeleton service instead.
|
|
642
|
+
row_limit: int
|
|
643
|
+
Passed to :func:`fetch_frames_batch`; guards against a silently-truncated pooled synapse
|
|
644
|
+
query at/above the server row limit (default 500,000). Set to 0 to disable.
|
|
630
645
|
|
|
631
646
|
Returns
|
|
632
647
|
-------
|
|
@@ -649,18 +664,20 @@ def load_cell_batch_from_client(
|
|
|
649
664
|
+ (" ..." if len(invalid_ids) > 5 else "")
|
|
650
665
|
)
|
|
651
666
|
|
|
652
|
-
# 2. Skeletons: one bulk download (chunked at the
|
|
653
|
-
# injected into assembly. Assumes a prior generate_bulk_skeletons_async pre-pass.
|
|
667
|
+
# 2. Skeletons: one cached bulk download (chunked at the cached-bulk cap) to obtain lvl2_ids,
|
|
668
|
+
# then injected into assembly. Assumes a prior generate_bulk_skeletons_async pre-pass.
|
|
654
669
|
sk_by_root = _get_bulk_skeletons(
|
|
655
|
-
client,
|
|
670
|
+
client,
|
|
671
|
+
valid_ids,
|
|
672
|
+
skeleton_version,
|
|
673
|
+
method=skeleton_download_method,
|
|
656
674
|
)
|
|
657
675
|
no_skeleton = [r for r in valid_ids if r not in sk_by_root]
|
|
658
676
|
if no_skeleton and not skip_invalid:
|
|
659
677
|
raise ValueError(
|
|
660
|
-
f"{len(no_skeleton)} root id(s) have no
|
|
661
|
-
f"(run generate_bulk_skeletons_async first, or pass
|
|
662
|
-
f"
|
|
663
|
-
+ (" ..." if len(no_skeleton) > 5 else "")
|
|
678
|
+
f"{len(no_skeleton)} root id(s) have no cached skeleton "
|
|
679
|
+
f"(run generate_bulk_skeletons_async first, or pass skip_invalid=True): "
|
|
680
|
+
f"{no_skeleton[:5]}" + (" ..." if len(no_skeleton) > 5 else "")
|
|
664
681
|
)
|
|
665
682
|
valid_ids = [r for r in valid_ids if r in sk_by_root]
|
|
666
683
|
lvl2_ids_by_root = {r: sk_by_root[r]["lvl2_ids"] for r in valid_ids}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|