ossify 0.2.7__tar.gz → 0.2.8__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.7 → ossify-0.2.8}/PKG-INFO +1 -1
- {ossify-0.2.7 → ossify-0.2.8}/pyproject.toml +2 -2
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/__init__.py +1 -1
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/base.py +54 -7
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/data_layers.py +120 -52
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/plot.py +25 -1
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/translate.py +5 -8
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/utils.py +4 -1
- {ossify-0.2.7 → ossify-0.2.8}/.github/workflows/mkdocs_publish.yml +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/.github/workflows/python-package.yml +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/.gitignore +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/.pre-commit-config.yaml +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/LICENSE +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/README.md +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/mkdocs.yml +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/_sync/__init__.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/_sync/base.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/_sync/graph.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/_sync/mapping.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/_sync/mesh.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/_sync/morph.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/_sync/points.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/_sync/table.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/algorithms.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/compartment_models/minnie65_ds15_us0_bd0.json +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/compartment_models/v1dd_ds15_us0_bd0.json +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/compartment_models/xgb/minnie65_ds15_us0_bd0.ubj +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/compartment_models/xgb/v1dd_ds15_us0_bd0.ubj +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/compartments.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/file_io.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/graph_functions.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/plot3d.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/plot_utils.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/structured_prediction.py +0 -0
- {ossify-0.2.7 → ossify-0.2.8}/src/ossify/sync_classes.py +0 -0
|
@@ -6,7 +6,7 @@ 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.8"
|
|
10
10
|
description = "Mesh and skeleton analysis"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.11"
|
|
@@ -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.8"
|
|
99
99
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
100
100
|
serialize = ["{major}.{minor}.{patch}"]
|
|
101
101
|
regex = false
|
|
@@ -398,6 +398,7 @@ class Cell:
|
|
|
398
398
|
features: Optional[Union[dict, pd.DataFrame]] = None,
|
|
399
399
|
*,
|
|
400
400
|
vertex_index: Optional[Union[str, np.ndarray]] = None,
|
|
401
|
+
faces_as_positional: Optional[bool] = None,
|
|
401
402
|
linkage: Optional[Link] = None,
|
|
402
403
|
spatial_columns: Optional[list] = None,
|
|
403
404
|
) -> Self:
|
|
@@ -408,11 +409,25 @@ class Cell:
|
|
|
408
409
|
vertices : Union[np.ndarray, pd.DataFrame, MeshLayer]
|
|
409
410
|
The vertices of the mesh, or a MeshLayer object.
|
|
410
411
|
faces : Union[np.ndarray, pd.DataFrame]
|
|
411
|
-
The faces of the mesh. If
|
|
412
|
+
The triangular faces of the mesh. **If ``vertex_index`` is given
|
|
413
|
+
these are positional indices into ``vertices``** and are remapped
|
|
414
|
+
onto the vertex index for you; otherwise they are vertex indices
|
|
415
|
+
already -- the index of ``vertices`` when it is a DataFrame, or
|
|
416
|
+
positional when it is a plain array. Mesh formats such as OBJ and
|
|
417
|
+
PLY give positional faces, so ``vertex_index`` is usually what you
|
|
418
|
+
want when labelling mesh vertices with ids.
|
|
412
419
|
features : Optional[Union[dict, pd.DataFrame]]
|
|
413
420
|
Additional features for the mesh. If passed as dictionary, the key is the feature name and the values are an array of feature values.
|
|
414
421
|
vertex_index : Optional[Union[str, np.ndarray]]
|
|
415
|
-
|
|
422
|
+
Vertex indices to label the mesh's vertices by: either the name of
|
|
423
|
+
a column in ``vertices``, or an array with one value per vertex.
|
|
424
|
+
Supplying this changes how ``faces`` are read -- see above.
|
|
425
|
+
faces_as_positional : Optional[bool]
|
|
426
|
+
Whether ``faces`` are positional indices into ``vertices``. The
|
|
427
|
+
default, None, infers it as described above: positional exactly
|
|
428
|
+
when ``vertex_index`` is supplied. Pass it explicitly when the two
|
|
429
|
+
spaces overlap -- a ``vertex_index`` whose values are also valid
|
|
430
|
+
positions cannot be told apart any other way.
|
|
416
431
|
linkage : Optional[Link]
|
|
417
432
|
The linkage information for the mesh.
|
|
418
433
|
spatial_columns: Optional[list] = None
|
|
@@ -435,6 +450,7 @@ class Cell:
|
|
|
435
450
|
name=self.MESH_LN,
|
|
436
451
|
vertices=vertices,
|
|
437
452
|
faces=faces,
|
|
453
|
+
faces_as_positional=faces_as_positional,
|
|
438
454
|
features=features,
|
|
439
455
|
morphsync=self._morphsync,
|
|
440
456
|
spatial_columns=spatial_columns,
|
|
@@ -453,6 +469,7 @@ class Cell:
|
|
|
453
469
|
root: Optional[int] = None,
|
|
454
470
|
*,
|
|
455
471
|
vertex_index: Optional[Union[str, np.ndarray]] = None,
|
|
472
|
+
edges_as_positional: Optional[bool] = None,
|
|
456
473
|
linkage: Optional[Link] = None,
|
|
457
474
|
spatial_columns: Optional[list] = None,
|
|
458
475
|
inherited_properties: Optional[dict] = None,
|
|
@@ -465,13 +482,28 @@ class Cell:
|
|
|
465
482
|
vertices : Union[np.ndarray, pd.DataFrame, SkeletonLayer]
|
|
466
483
|
The vertices of the skeleton, or a SkeletonLayer object.
|
|
467
484
|
edges : Union[np.ndarray, pd.DataFrame]
|
|
468
|
-
The edges of the skeleton.
|
|
485
|
+
The edges of the skeleton, as pairs of vertices. **If ``vertex_index``
|
|
486
|
+
is given these are positional indices into ``vertices``** and are
|
|
487
|
+
remapped onto the vertex index for you; otherwise they are vertex
|
|
488
|
+
indices already -- the index of ``vertices`` when it is a DataFrame,
|
|
489
|
+
or positional when it is a plain array.
|
|
469
490
|
features : Optional[Union[dict, pd.DataFrame]]
|
|
470
491
|
The features for the skeleton.
|
|
471
492
|
root : Optional[int]
|
|
472
|
-
The root
|
|
493
|
+
The root of the skeleton, as a **vertex index** -- note this is the
|
|
494
|
+
opposite convention to ``edges``, which are positional when
|
|
495
|
+
``vertex_index`` is given. Required if the edges are not already
|
|
496
|
+
consistent with a single root.
|
|
473
497
|
vertex_index : Optional[Union[str, np.ndarray]]
|
|
474
|
-
|
|
498
|
+
Vertex indices to label the skeleton's vertices by: either the name of
|
|
499
|
+
a column in ``vertices``, or an array with one value per vertex.
|
|
500
|
+
Supplying this changes how ``edges`` are read -- see above.
|
|
501
|
+
edges_as_positional : Optional[bool]
|
|
502
|
+
Whether ``edges`` are positional indices into ``vertices``. The
|
|
503
|
+
default, None, infers it as described above: positional exactly
|
|
504
|
+
when ``vertex_index`` is supplied. Pass it explicitly when the two
|
|
505
|
+
spaces overlap -- a ``vertex_index`` whose values are also valid
|
|
506
|
+
positions cannot be told apart any other way.
|
|
475
507
|
linkage : Optional[Link]
|
|
476
508
|
The linkage information for the skeleton. Typically, you will define the source vertices for the skeleton if using a graph-to-skeleton mapping.
|
|
477
509
|
spatial_columns: Optional[list] = None
|
|
@@ -495,6 +527,7 @@ class Cell:
|
|
|
495
527
|
name=self.SKEL_LN,
|
|
496
528
|
vertices=vertices,
|
|
497
529
|
edges=edges,
|
|
530
|
+
edges_as_positional=edges_as_positional,
|
|
498
531
|
features=features,
|
|
499
532
|
root=root,
|
|
500
533
|
morphsync=self._morphsync,
|
|
@@ -514,6 +547,7 @@ class Cell:
|
|
|
514
547
|
features: Optional[Union[dict, pd.DataFrame]] = None,
|
|
515
548
|
*,
|
|
516
549
|
vertex_index: Optional[Union[str, np.ndarray]] = None,
|
|
550
|
+
edges_as_positional: Optional[bool] = None,
|
|
517
551
|
spatial_columns: Optional[list] = None,
|
|
518
552
|
linkage: Optional[Link] = None,
|
|
519
553
|
) -> Self:
|
|
@@ -526,11 +560,23 @@ class Cell:
|
|
|
526
560
|
vertices : Union[np.ndarray, pd.DataFrame, SkeletonLayer]
|
|
527
561
|
The vertices of the graph.
|
|
528
562
|
edges : Union[np.ndarray, pd.DataFrame]
|
|
529
|
-
The edges of the graph.
|
|
563
|
+
The edges of the graph, as pairs of vertices. **If ``vertex_index``
|
|
564
|
+
is given these are positional indices into ``vertices``** and are
|
|
565
|
+
remapped onto the vertex index for you; otherwise they are vertex
|
|
566
|
+
indices already -- the index of ``vertices`` when it is a DataFrame,
|
|
567
|
+
or positional when it is a plain array.
|
|
530
568
|
features : Optional[Union[dict, pd.DataFrame]]
|
|
531
569
|
The features for the graph.
|
|
532
570
|
vertex_index : Optional[Union[str, np.ndarray]]
|
|
533
|
-
|
|
571
|
+
Vertex indices to label the graph's vertices by: either the name of
|
|
572
|
+
a column in ``vertices``, or an array with one value per vertex.
|
|
573
|
+
Supplying this changes how ``edges`` are read -- see above.
|
|
574
|
+
edges_as_positional : Optional[bool]
|
|
575
|
+
Whether ``edges`` are positional indices into ``vertices``. The
|
|
576
|
+
default, None, infers it as described above: positional exactly
|
|
577
|
+
when ``vertex_index`` is supplied. Pass it explicitly when the two
|
|
578
|
+
spaces overlap -- a ``vertex_index`` whose values are also valid
|
|
579
|
+
positions cannot be told apart any other way.
|
|
534
580
|
spatial_columns: Optional[list] = None
|
|
535
581
|
The spatial columns for the graph, if vertices are a dataframe.
|
|
536
582
|
|
|
@@ -552,6 +598,7 @@ class Cell:
|
|
|
552
598
|
name=self.GRAPH_LN,
|
|
553
599
|
vertices=vertices,
|
|
554
600
|
edges=edges,
|
|
601
|
+
edges_as_positional=edges_as_positional,
|
|
555
602
|
features=features,
|
|
556
603
|
morphsync=self._morphsync,
|
|
557
604
|
spatial_columns=spatial_columns,
|
|
@@ -89,26 +89,6 @@ class EdgeMixin(ABC):
|
|
|
89
89
|
return np.empty((0, 2), dtype=int)
|
|
90
90
|
return self.layer.edges_positional
|
|
91
91
|
|
|
92
|
-
def _map_edges_to_index(
|
|
93
|
-
self, edges: np.ndarray, vertex_indices: np.ndarray
|
|
94
|
-
) -> np.ndarray:
|
|
95
|
-
"""Remap positional edges to vertex indices.
|
|
96
|
-
|
|
97
|
-
Parameters
|
|
98
|
-
----------
|
|
99
|
-
edges : np.ndarray
|
|
100
|
-
Edge array with positional indices.
|
|
101
|
-
vertex_indices : np.ndarray
|
|
102
|
-
Array of vertex indices to map to.
|
|
103
|
-
|
|
104
|
-
Returns
|
|
105
|
-
-------
|
|
106
|
-
np.ndarray
|
|
107
|
-
Edge array with vertex indices instead of positional indices.
|
|
108
|
-
"""
|
|
109
|
-
index_map = {ii: v for ii, v in enumerate(vertex_indices)}
|
|
110
|
-
return fastremap.remap(edges, index_map)
|
|
111
|
-
|
|
112
92
|
@property
|
|
113
93
|
def csgraph(self) -> sparse.csr_matrix:
|
|
114
94
|
"""
|
|
@@ -318,26 +298,6 @@ class FaceMixin(ABC):
|
|
|
318
298
|
self._csgraph = None
|
|
319
299
|
self._trimesh = None
|
|
320
300
|
|
|
321
|
-
def _map_faces_to_index(
|
|
322
|
-
self, faces: np.ndarray, vertex_indices: np.ndarray
|
|
323
|
-
) -> np.ndarray:
|
|
324
|
-
"""Remap positional faces to vertex indices.
|
|
325
|
-
|
|
326
|
-
Parameters
|
|
327
|
-
----------
|
|
328
|
-
faces : np.ndarray
|
|
329
|
-
Face array with positional indices.
|
|
330
|
-
vertex_indices : np.ndarray
|
|
331
|
-
Array of vertex indices to map to.
|
|
332
|
-
|
|
333
|
-
Returns
|
|
334
|
-
-------
|
|
335
|
-
np.ndarray
|
|
336
|
-
Face array with vertex indices instead of positional indices.
|
|
337
|
-
"""
|
|
338
|
-
index_map = {ii: v for ii, v in enumerate(vertex_indices)}
|
|
339
|
-
return fastremap.remap(faces, index_map)
|
|
340
|
-
|
|
341
301
|
def surface_area(
|
|
342
302
|
self,
|
|
343
303
|
vertices: Optional[np.ndarray] = None,
|
|
@@ -444,6 +404,75 @@ class PointMixin(ABC):
|
|
|
444
404
|
)
|
|
445
405
|
self._process_linkage(linkage)
|
|
446
406
|
|
|
407
|
+
def _index_connectivity(
|
|
408
|
+
self,
|
|
409
|
+
connectivity: np.ndarray,
|
|
410
|
+
vertices: pd.DataFrame,
|
|
411
|
+
as_positional: Optional[bool],
|
|
412
|
+
vertex_index: Optional[Union[str, np.ndarray]],
|
|
413
|
+
what: str = "edges",
|
|
414
|
+
) -> np.ndarray:
|
|
415
|
+
"""Put edges or faces into vertex-index space at construction.
|
|
416
|
+
|
|
417
|
+
Every layer type takes connectivity that may be written either as
|
|
418
|
+
positional indices into ``vertices`` or as vertex indices, so the rule
|
|
419
|
+
lives here once rather than in each constructor.
|
|
420
|
+
|
|
421
|
+
``as_positional=None`` keeps the historical behaviour -- positional
|
|
422
|
+
exactly when ``vertex_index`` was supplied -- so callers that never
|
|
423
|
+
pass the flag are unaffected. An explicit ``True``/``False`` overrides
|
|
424
|
+
it, and is the only way to disambiguate a vertex index whose values
|
|
425
|
+
are also valid positions.
|
|
426
|
+
|
|
427
|
+
Parameters
|
|
428
|
+
----------
|
|
429
|
+
connectivity : np.ndarray
|
|
430
|
+
Edge pairs or face triples.
|
|
431
|
+
vertices : pd.DataFrame
|
|
432
|
+
The processed vertex table; its index supplies the vertex indices.
|
|
433
|
+
as_positional : Optional[bool]
|
|
434
|
+
Whether ``connectivity`` holds positional indices. None infers it.
|
|
435
|
+
vertex_index : Optional[Union[str, np.ndarray]]
|
|
436
|
+
The vertex index argument, used only for the inference above.
|
|
437
|
+
what : str
|
|
438
|
+
Name used in error messages -- "edges" or "faces".
|
|
439
|
+
|
|
440
|
+
Returns
|
|
441
|
+
-------
|
|
442
|
+
np.ndarray
|
|
443
|
+
``connectivity`` in vertex-index space.
|
|
444
|
+
"""
|
|
445
|
+
positional = (
|
|
446
|
+
vertex_index is not None if as_positional is None else bool(as_positional)
|
|
447
|
+
)
|
|
448
|
+
if not positional:
|
|
449
|
+
return connectivity
|
|
450
|
+
|
|
451
|
+
arr = np.asarray(connectivity)
|
|
452
|
+
if arr.size:
|
|
453
|
+
# Without this check, values that are actually vertex indices
|
|
454
|
+
# surface as KeyError from inside fastremap, which says nothing
|
|
455
|
+
# about the convention that was violated.
|
|
456
|
+
low, high = int(arr.min()), int(arr.max())
|
|
457
|
+
if low < 0 or high >= len(vertices):
|
|
458
|
+
if as_positional is None:
|
|
459
|
+
hint = (
|
|
460
|
+
f"`{what}` are read as positional indices because "
|
|
461
|
+
f"`vertex_index` was given; pass "
|
|
462
|
+
f"{what}_as_positional=False if they are vertex "
|
|
463
|
+
"indices already."
|
|
464
|
+
)
|
|
465
|
+
else:
|
|
466
|
+
hint = (
|
|
467
|
+
f"`{what}_as_positional=True` was given, so `{what}` "
|
|
468
|
+
"must be positional indices."
|
|
469
|
+
)
|
|
470
|
+
raise ValueError(
|
|
471
|
+
f"{what} contain values outside the valid positional range "
|
|
472
|
+
f"0..{len(vertices) - 1} (found {low}..{high}). {hint}"
|
|
473
|
+
)
|
|
474
|
+
return fastremap.remap(arr, dict(enumerate(vertices.index)))
|
|
475
|
+
|
|
447
476
|
def _vertices_to_positional(
|
|
448
477
|
self,
|
|
449
478
|
vertices: Optional[np.ndarray],
|
|
@@ -798,8 +827,17 @@ class PointMixin(ABC):
|
|
|
798
827
|
right_index=True,
|
|
799
828
|
how="left",
|
|
800
829
|
)
|
|
830
|
+
# "majority" is ossify's own reducer, not something pandas understands,
|
|
831
|
+
# so it has to be swapped for the callable wherever it appears -- including
|
|
832
|
+
# inside a per-feature dict, which is the documented way to give different
|
|
833
|
+
# features different aggregations.
|
|
801
834
|
if agg == "majority":
|
|
802
835
|
agg = utils.majority_agg()
|
|
836
|
+
elif isinstance(agg, dict):
|
|
837
|
+
agg = {
|
|
838
|
+
key: utils.majority_agg() if value == "majority" else value
|
|
839
|
+
for key, value in agg.items()
|
|
840
|
+
}
|
|
803
841
|
# Group by target layer and aggregate, then reindex to ensure all target vertices are included
|
|
804
842
|
grouped_result = mapping_merged.groupby(layer).agg(agg)
|
|
805
843
|
target_layer_index = self._morphsync.layers[layer].nodes.index
|
|
@@ -1498,6 +1536,7 @@ class GraphLayer(PointMixin, EdgeMixin):
|
|
|
1498
1536
|
spatial_columns: Optional[list] = None,
|
|
1499
1537
|
*,
|
|
1500
1538
|
vertex_index: Optional[Union[str, np.ndarray]] = None,
|
|
1539
|
+
edges_as_positional: Optional[bool] = None,
|
|
1501
1540
|
features: Optional[Union[dict, pd.DataFrame]] = None,
|
|
1502
1541
|
morphsync: MorphSync = None,
|
|
1503
1542
|
linkage: Optional[Link] = None,
|
|
@@ -1515,8 +1554,9 @@ class GraphLayer(PointMixin, EdgeMixin):
|
|
|
1515
1554
|
self._cell = None
|
|
1516
1555
|
|
|
1517
1556
|
if not existing:
|
|
1518
|
-
|
|
1519
|
-
edges
|
|
1557
|
+
edges = self._index_connectivity(
|
|
1558
|
+
edges, vertices, edges_as_positional, vertex_index, "edges"
|
|
1559
|
+
)
|
|
1520
1560
|
self._morphsync.add_graph(
|
|
1521
1561
|
graph=(vertices, edges),
|
|
1522
1562
|
name=self.layer_name,
|
|
@@ -1869,6 +1909,7 @@ class SkeletonLayer(GraphLayer):
|
|
|
1869
1909
|
root: Optional[int] = None,
|
|
1870
1910
|
*,
|
|
1871
1911
|
vertex_index: Optional[Union[str, np.ndarray]] = None,
|
|
1912
|
+
edges_as_positional: Optional[bool] = None,
|
|
1872
1913
|
features: Optional[Union[dict, pd.DataFrame]] = None,
|
|
1873
1914
|
morphsync: MorphSync = None,
|
|
1874
1915
|
linkage: Optional[dict] = None,
|
|
@@ -1885,8 +1926,9 @@ class SkeletonLayer(GraphLayer):
|
|
|
1885
1926
|
|
|
1886
1927
|
if inherited_properties is None:
|
|
1887
1928
|
# Add as a morphsync layer
|
|
1888
|
-
|
|
1889
|
-
edges
|
|
1929
|
+
edges = self._index_connectivity(
|
|
1930
|
+
edges, vertices, edges_as_positional, vertex_index, "edges"
|
|
1931
|
+
)
|
|
1890
1932
|
self._morphsync.add_graph(
|
|
1891
1933
|
graph=(vertices, edges),
|
|
1892
1934
|
name=self.layer_name,
|
|
@@ -2247,24 +2289,38 @@ class SkeletonLayer(GraphLayer):
|
|
|
2247
2289
|
Parameters
|
|
2248
2290
|
----------
|
|
2249
2291
|
root : Optional[int]
|
|
2250
|
-
Proposed root
|
|
2292
|
+
Proposed root, as a **vertex index** -- the same space as
|
|
2293
|
+
``vertex_index``, not a positional index. Note this differs from
|
|
2294
|
+
``edges``, which are positional when ``vertex_index`` is supplied.
|
|
2295
|
+
If None, attempts to infer from graph structure.
|
|
2251
2296
|
|
|
2252
2297
|
Returns
|
|
2253
2298
|
-------
|
|
2254
2299
|
int
|
|
2255
|
-
The root
|
|
2300
|
+
The root vertex index.
|
|
2256
2301
|
|
|
2257
2302
|
Raises
|
|
2258
2303
|
------
|
|
2259
2304
|
ValueError
|
|
2260
|
-
If
|
|
2305
|
+
If the given root is not one of this layer's vertices, or if no
|
|
2306
|
+
root was specified and multiple potential roots were found.
|
|
2261
2307
|
"""
|
|
2262
2308
|
if root is not None:
|
|
2263
|
-
|
|
2309
|
+
root = int(root)
|
|
2310
|
+
# Numpy membership rather than building a Python set: a layer can
|
|
2311
|
+
# have millions of vertices and this runs on every construction.
|
|
2312
|
+
if not np.any(self.vertex_index == root):
|
|
2313
|
+
raise ValueError(
|
|
2314
|
+
f"root {root} is not a vertex of this layer. `root` is a "
|
|
2315
|
+
"vertex index, not a positional index -- note that `edges` "
|
|
2316
|
+
"use the opposite convention when `vertex_index` is given."
|
|
2317
|
+
)
|
|
2318
|
+
return root
|
|
2264
2319
|
else:
|
|
2265
2320
|
potential_roots = np.flatnonzero(self.csgraph_binary.sum(axis=1) == 0)
|
|
2266
2321
|
if len(potential_roots) == 1:
|
|
2267
|
-
|
|
2322
|
+
# ``potential_roots`` is positional; the root is a vertex index.
|
|
2323
|
+
return int(self.vertex_index[potential_roots[0]])
|
|
2268
2324
|
else:
|
|
2269
2325
|
raise ValueError(
|
|
2270
2326
|
"No root specified and edges are not consistent with a single root. Please set a valid root."
|
|
@@ -2336,6 +2392,16 @@ class SkeletonLayer(GraphLayer):
|
|
|
2336
2392
|
edges_positional_new[:, ii]
|
|
2337
2393
|
]
|
|
2338
2394
|
|
|
2395
|
+
# The edges just changed direction, but ``csgraph``/``csgraph_binary``
|
|
2396
|
+
# were built (by the connected_components call above) from the *old*
|
|
2397
|
+
# orientation. Everything read off the directed graph -- branch points,
|
|
2398
|
+
# end points, segments, cover paths -- would otherwise be computed from
|
|
2399
|
+
# a stale graph that disagrees with ``edges_positional``. Drop only the
|
|
2400
|
+
# edge-derived caches: the DAG cache holds root/parent state that the
|
|
2401
|
+
# callers set around this method.
|
|
2402
|
+
self._csgraph = None
|
|
2403
|
+
self._csgraph_binary = None
|
|
2404
|
+
|
|
2339
2405
|
def reroot(self, new_root: int, as_positional=False) -> Self:
|
|
2340
2406
|
"""Reroot to a new index. Important: that this will reset any inherited properties from an unmasked skeleton!
|
|
2341
2407
|
|
|
@@ -2641,7 +2707,7 @@ class SkeletonLayer(GraphLayer):
|
|
|
2641
2707
|
A list of cover paths, each path is a list of vertex indices, ordered as the typical `cover_paths` method.
|
|
2642
2708
|
"""
|
|
2643
2709
|
sources, as_positional = self._vertices_to_positional(sources, as_positional)
|
|
2644
|
-
cps = gf.
|
|
2710
|
+
cps = gf.build_cover_paths(
|
|
2645
2711
|
sources,
|
|
2646
2712
|
self.parent_node_array,
|
|
2647
2713
|
self.distance_to_root(as_positional=True),
|
|
@@ -3479,6 +3545,7 @@ class MeshLayer(FaceMixin, PointMixin):
|
|
|
3479
3545
|
spatial_columns: Optional[list] = None,
|
|
3480
3546
|
*,
|
|
3481
3547
|
vertex_index: Optional[Union[str, np.ndarray]] = None,
|
|
3548
|
+
faces_as_positional: Optional[bool] = None,
|
|
3482
3549
|
features: Optional[Union[dict, pd.DataFrame]] = None,
|
|
3483
3550
|
morphsync: MorphSync = None,
|
|
3484
3551
|
linkage: Optional[Link] = None,
|
|
@@ -3496,8 +3563,9 @@ class MeshLayer(FaceMixin, PointMixin):
|
|
|
3496
3563
|
self._cell = None
|
|
3497
3564
|
|
|
3498
3565
|
if not existing:
|
|
3499
|
-
|
|
3500
|
-
faces
|
|
3566
|
+
faces = self._index_connectivity(
|
|
3567
|
+
faces, vertices, faces_as_positional, vertex_index, "faces"
|
|
3568
|
+
)
|
|
3501
3569
|
self._morphsync.add_mesh(
|
|
3502
3570
|
mesh=(vertices, faces),
|
|
3503
3571
|
name=self.layer_name,
|
|
@@ -981,7 +981,31 @@ def plot_points(
|
|
|
981
981
|
# Single color string
|
|
982
982
|
scatter_kws["color"] = colors
|
|
983
983
|
else:
|
|
984
|
-
|
|
984
|
+
colors_arr = np.asarray(colors)
|
|
985
|
+
single_rgba = (
|
|
986
|
+
colors_arr.ndim == 1
|
|
987
|
+
and colors_arr.shape[0] in (3, 4)
|
|
988
|
+
and np.issubdtype(colors_arr.dtype, np.number)
|
|
989
|
+
and colors_arr.shape[0] != len(points_proj)
|
|
990
|
+
)
|
|
991
|
+
if single_rgba:
|
|
992
|
+
# One bare RGB/RGBA tuple means "this exact color", but as `c`
|
|
993
|
+
# it is ambiguous with one scalar value per point, and
|
|
994
|
+
# matplotlib warns. A 2-D single row is its documented way to
|
|
995
|
+
# say the former. Reached via root_marker, whose color is read
|
|
996
|
+
# off the root vertex as a single RGBA.
|
|
997
|
+
colors_arr = colors_arr.reshape(1, -1)
|
|
998
|
+
if (
|
|
999
|
+
colors_arr.ndim == 2
|
|
1000
|
+
and colors_arr.shape[1] in (3, 4)
|
|
1001
|
+
and np.issubdtype(colors_arr.dtype, np.number)
|
|
1002
|
+
):
|
|
1003
|
+
# Explicit RGB/RGBA values are not colormapped, so a cmap set
|
|
1004
|
+
# from a string palette above would be silently ignored --
|
|
1005
|
+
# matplotlib says so out loud. Drop it and its norm.
|
|
1006
|
+
for key in ("cmap", "vmin", "vmax"):
|
|
1007
|
+
scatter_kws.pop(key, None)
|
|
1008
|
+
scatter_kws["c"] = colors_arr
|
|
985
1009
|
|
|
986
1010
|
ax.scatter(
|
|
987
1011
|
x=points_proj[:, 0],
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from enum import IntEnum
|
|
2
2
|
from typing import TYPE_CHECKING, Literal, Optional, Tuple, Union
|
|
3
3
|
|
|
4
|
-
import fastremap
|
|
5
4
|
import numpy as np
|
|
6
5
|
import pandas as pd
|
|
7
6
|
|
|
@@ -310,13 +309,10 @@ def load_cell_from_client(
|
|
|
310
309
|
l2_df = l2_df.reset_index()
|
|
311
310
|
|
|
312
311
|
if restore_graph:
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
edges =
|
|
317
|
-
l2_graph,
|
|
318
|
-
l2_map,
|
|
319
|
-
)
|
|
312
|
+
# The chunkedgraph gives edges as l2_id pairs, which is what the graph
|
|
313
|
+
# is indexed by, so hand them over as-is rather than round-tripping
|
|
314
|
+
# them through positional indices and straight back again.
|
|
315
|
+
edges = client.chunkedgraph.level2_chunk_graph(root_id)
|
|
320
316
|
else:
|
|
321
317
|
edges = []
|
|
322
318
|
|
|
@@ -335,6 +331,7 @@ def load_cell_from_client(
|
|
|
335
331
|
spatial_columns=l2_spatial_columns,
|
|
336
332
|
edges=edges,
|
|
337
333
|
vertex_index="l2_id",
|
|
334
|
+
edges_as_positional=False,
|
|
338
335
|
)
|
|
339
336
|
.add_skeleton(
|
|
340
337
|
vertices=np.array(sk["vertices"]),
|
|
@@ -79,7 +79,10 @@ def process_vertices(
|
|
|
79
79
|
)
|
|
80
80
|
spatial_columns = vertices.columns
|
|
81
81
|
else:
|
|
82
|
-
|
|
82
|
+
# Only a string names a column to hold out of the implicit features; an
|
|
83
|
+
# array-like vertex_index supplies the ids directly. Plain truthiness
|
|
84
|
+
# raises on an array of more than one element.
|
|
85
|
+
if isinstance(vertex_index, str):
|
|
83
86
|
implicit_feature_columns = list(
|
|
84
87
|
vertices.columns[
|
|
85
88
|
~vertices.columns.isin(spatial_columns + [vertex_index])
|
|
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
|