morphomatics 4.0__py3-none-any.whl

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.
Files changed (54) hide show
  1. morphomatics/__init__.py +13 -0
  2. morphomatics/geom/__init__.py +16 -0
  3. morphomatics/geom/bezier_spline.py +361 -0
  4. morphomatics/geom/misc.py +104 -0
  5. morphomatics/geom/surface.py +208 -0
  6. morphomatics/graph/__init__.py +13 -0
  7. morphomatics/graph/operators.py +124 -0
  8. morphomatics/manifold/__init__.py +46 -0
  9. morphomatics/manifold/bezierfold.py +500 -0
  10. morphomatics/manifold/connection.py +105 -0
  11. morphomatics/manifold/cubic_bezierfold.py +305 -0
  12. morphomatics/manifold/differential_coords.py +197 -0
  13. morphomatics/manifold/discrete_ops.py +56 -0
  14. morphomatics/manifold/euclidean.py +213 -0
  15. morphomatics/manifold/fundamental_coords.py +440 -0
  16. morphomatics/manifold/gl_p_coords.py +149 -0
  17. morphomatics/manifold/gl_p_n.py +201 -0
  18. morphomatics/manifold/grassmann.py +174 -0
  19. morphomatics/manifold/hyperbolic_space.py +271 -0
  20. morphomatics/manifold/kendall.py +269 -0
  21. morphomatics/manifold/lie_group.py +102 -0
  22. morphomatics/manifold/manifold.py +162 -0
  23. morphomatics/manifold/manopt_wrapper.py +185 -0
  24. morphomatics/manifold/metric.py +110 -0
  25. morphomatics/manifold/point_distribution_model.py +143 -0
  26. morphomatics/manifold/power_manifold.py +413 -0
  27. morphomatics/manifold/product_manifold.py +381 -0
  28. morphomatics/manifold/se_3.py +419 -0
  29. morphomatics/manifold/shape_space.py +57 -0
  30. morphomatics/manifold/so_3.py +494 -0
  31. morphomatics/manifold/spd.py +524 -0
  32. morphomatics/manifold/sphere.py +241 -0
  33. morphomatics/manifold/tangent_bundle.py +337 -0
  34. morphomatics/manifold/util.py +126 -0
  35. morphomatics/nn/__init__.py +15 -0
  36. morphomatics/nn/flow_layers.py +219 -0
  37. morphomatics/nn/tangent_layers.py +176 -0
  38. morphomatics/nn/train.py +202 -0
  39. morphomatics/nn/wFM_layers.py +152 -0
  40. morphomatics/opt/__init__.py +14 -0
  41. morphomatics/opt/riemannian_newton_raphson.py +65 -0
  42. morphomatics/opt/riemannian_steepest_descent.py +61 -0
  43. morphomatics/stats/__init__.py +18 -0
  44. morphomatics/stats/biinvariant_statistics.py +190 -0
  45. morphomatics/stats/exponential_barycenter.py +78 -0
  46. morphomatics/stats/geometric_median.py +89 -0
  47. morphomatics/stats/principal_geodesic_analysis.py +135 -0
  48. morphomatics/stats/riemannian_regression.py +317 -0
  49. morphomatics/stats/statistical_shape_model.py +99 -0
  50. morphomatics-4.0.dist-info/LICENSE +9 -0
  51. morphomatics-4.0.dist-info/METADATA +55 -0
  52. morphomatics-4.0.dist-info/RECORD +54 -0
  53. morphomatics-4.0.dist-info/WHEEL +5 -0
  54. morphomatics-4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,208 @@
1
+ ################################################################################
2
+ # #
3
+ # This file is part of the Morphomatics library #
4
+ # see https://github.com/morphomatics/morphomatics #
5
+ # #
6
+ # Copyright (C) 2024 Zuse Institute Berlin #
7
+ # #
8
+ # Morphomatics is distributed under the terms of the MIT License. #
9
+ # see $MORPHOMATICS/LICENSE #
10
+ # #
11
+ ################################################################################
12
+
13
+
14
+ import numpy as np
15
+ from scipy import sparse
16
+
17
+ from .misc import memoize, gradient_matrix_ambient
18
+
19
+
20
+
21
+ class Surface(object):
22
+ """ Triangular surface geom. """
23
+
24
+ def __init__(self, v, f):
25
+ """
26
+ :arg v: #v-by-dim array of vertex coordinate
27
+ :arg f: #f-by-3 array of triangles (each given as indices into \a v)
28
+ """
29
+ # cache for memoization
30
+ self._cache_v = dict()
31
+ self._cache_f = dict()
32
+
33
+ self.v = np.asarray(v).astype(np.double, copy=False)
34
+ self.f = np.asarray(f)
35
+
36
+ @property
37
+ def v(self):
38
+ return self.__v
39
+
40
+ @v.setter
41
+ def v(self, v):
42
+ self.__v = v
43
+ self._cache_v.clear()
44
+
45
+ @property
46
+ def f(self):
47
+ return self.__f
48
+
49
+ @f.setter
50
+ def f(self, f):
51
+ self.__f = f
52
+ self._cache_v.clear()
53
+ self._cache_f.clear()
54
+
55
+ @property
56
+ @memoize('_cache_v')
57
+ def grad(self):
58
+ """ Gradient of a scalar function defined on piecewise linear elements
59
+ (see: geom.misc.gradient_matrix_ambient) """
60
+ return gradient_matrix_ambient(self.v, self.f)
61
+
62
+ @property
63
+ @memoize('_cache_v')
64
+ def div(self):
65
+ """Divergence operator"""
66
+ mass = self.face_areas
67
+ d = self.grad.shape[0] / len(mass) # ambient dim.
68
+ return self.grad.T @ sparse.diags(np.repeat(mass, d), 0)
69
+
70
+ @property
71
+ @memoize('_cache_v')
72
+ def face_areas(self):
73
+ """Area of triangles."""
74
+ # Compute cross-product of edges
75
+ ppts = self.v[self.f]
76
+ nnfnorms = np.cross(ppts[:, 1] - ppts[:, 0],
77
+ ppts[:, 2] - ppts[:, 0])
78
+ # Compute vector length
79
+ return np.sqrt((nnfnorms ** 2).sum(-1)) / 2
80
+
81
+ @property
82
+ @memoize('_cache_f')
83
+ def inner_edges(self):
84
+ """
85
+ Extract inner edges of an oriented, triangle surface.
86
+ :return: sparse matrix with inner edge ids as entries (indexed by resp. faces).
87
+ """
88
+ m = len(self.f)
89
+ n = self.f.max() + 1
90
+
91
+ e1 = sparse.coo_matrix((np.ones(m), (self.f[:, 0], self.f[:, 1])), (n, n))
92
+ e2 = sparse.coo_matrix((np.ones(m), (self.f[:, 1], self.f[:, 2])), (n, n))
93
+ e3 = sparse.coo_matrix((np.ones(m), (self.f[:, 2], self.f[:, 0])), (n, n))
94
+ D = (e1 + e2 + e3).tocsr()
95
+
96
+ # boundary edges as sparse matrix
97
+ E = (D - D.T).tocsr()
98
+ # inner edges as sparse matrix
99
+ F = 0.5*(D + D.T - np.absolute(D - D.T))
100
+ F.eliminate_zeros()
101
+
102
+ off = 1
103
+ f1 = sparse.coo_matrix((np.arange(off, m+off), (self.f[:, 0], self.f[:, 1])), (n, n)).tocsr()
104
+ f2 = sparse.coo_matrix((np.arange(off, m+off), (self.f[:, 1], self.f[:, 2])), (n, n)).tocsr()
105
+ f3 = sparse.coo_matrix((np.arange(off, m+off), (self.f[:, 2], self.f[:, 0])), (n, n)).tocsr()
106
+
107
+ ff = f1 +f2 +f3
108
+
109
+ FF = F.multiply(ff)
110
+ FFU = sparse.triu(FF).tocsr()
111
+
112
+ edgesI = FFU.tocoo().row
113
+ edgesJ = FFU.tocoo().col
114
+
115
+ edgeTriI = np.reshape(np.asarray(FF[edgesI[:], edgesJ[:]]), edgesI.shape[0]) - 1
116
+ edgeTriJ = np.reshape(np.asarray(FF[edgesJ[:], edgesI[:]]), edgesJ.shape[0]) - 1
117
+
118
+ # k-th (inner) edge, s.t. FFU(edgesI[k], edgesJ[k]) -> FFU(edgesJ[k], edgesI[k])
119
+ tris2InnerEdgeId = sparse.coo_matrix((np.arange(1, FFU.getnnz()+1), (edgeTriI[:], edgeTriJ[:])), (m, m))
120
+ tris2InnerEdgeId = tris2InnerEdgeId + tris2InnerEdgeId.T
121
+ tris2InnerEdgeId.data = tris2InnerEdgeId.data -1
122
+
123
+ return tris2InnerEdgeId.tocsr()
124
+
125
+ @property
126
+ @memoize('_cache_v')
127
+ def edge_areas(self):
128
+ inner_edge = sparse.triu(self.inner_edges)
129
+ areas=np.zeros(inner_edge.data.shape[0])
130
+ areas[inner_edge.data[:]] = 1.0/3.0 * ( self.face_areas[inner_edge.row[:]] + self.face_areas[inner_edge.col[:]] )
131
+ return areas
132
+
133
+ @property
134
+ @memoize('_cache_f')
135
+ def neighbors(self):
136
+ """
137
+ Provides information on face neighbors.
138
+ :return: idx_1, idx_2, idx_3, n_1, n_2, n_3,
139
+ where idx_1, idx_2, idx_3 are boolean index arrays for faces with at least one (idx_1), at least two (idx_2) and three (idx_3) neighbors;
140
+ where n_1, n_2, n_3 are n x k array storing the respective neighbors per face for all faces with at least one (n_1), at least two (n_2)
141
+ and three (n_3) neighbors.
142
+ """
143
+ # neighbors per face
144
+ neighs = np.asarray(np.split(self.inner_edges.indices, self.inner_edges.indptr[1:-1]), dtype=object)
145
+ nNeighs = np.asarray(list(map(np.shape, neighs))).ravel()
146
+
147
+ idx_1 = (nNeighs == 1)
148
+ idx_2 = (nNeighs == 2)
149
+ idx_3 = (nNeighs == 3)
150
+
151
+ neigh_pad = np.zeros((neighs.shape[0], 3)).astype(int)
152
+
153
+ if np.count_nonzero(idx_1):
154
+ neigh_pad[idx_1, 0:1] = np.asarray(list(neighs[idx_1]))
155
+ if np.count_nonzero(idx_2):
156
+ neigh_pad[idx_2, 0:2] = np.asarray(list(neighs[idx_2]))
157
+ if np.count_nonzero(idx_3):
158
+ neigh_pad[idx_3, 0:3] = np.asarray(list(neighs[idx_3]))
159
+
160
+ idx_1 = (nNeighs > 0)
161
+ idx_2 = (nNeighs > 1)
162
+ idx_3 = (nNeighs > 2)
163
+
164
+ n_1 = neigh_pad[idx_1]
165
+ n_2 = neigh_pad[idx_2]
166
+ n_3 = neigh_pad[idx_3]
167
+
168
+ return idx_1, idx_2, idx_3, n_1, n_2, n_3
169
+
170
+ def copy(self):
171
+ """
172
+ :return: copy of this surface
173
+ """
174
+ return Surface(self.v.copy(), self.f.copy())
175
+
176
+ def boundary(self):
177
+ """
178
+ Extract boundary curves of an oriented, triangle surface.
179
+ :return: List of boundary curves (list of vertex indices).
180
+ """
181
+ m = len(self.f)
182
+ n = self.f.max() + 1
183
+ # boundary edges as sparse matrix
184
+ e1 = sparse.coo_matrix((np.ones(m), (self.f[:, 0], self.f[:, 1])), (n, n))
185
+ e2 = sparse.coo_matrix((np.ones(m), (self.f[:, 1], self.f[:, 2])), (n, n))
186
+ e3 = sparse.coo_matrix((np.ones(m), (self.f[:, 2], self.f[:, 0])), (n, n))
187
+ E = (e1 - e1.T + e2 - e2.T + e3 - e3.T).tocsr()
188
+ E.sort_indices()
189
+
190
+ if E.nnz == 0:
191
+ return None
192
+
193
+ # extract boundary curves
194
+ boundaries = []
195
+
196
+ nnz = E.getnnz(1)
197
+ for i in range(n):
198
+ if nnz[i] == 0: continue
199
+ # new boundary curve
200
+ bnd = [i]
201
+ while True:
202
+ j = (E.getrow(bnd[-1]) > 0).indices[0]
203
+ nnz[j] = 0 # mark as part of a processed bnd. curve
204
+ if j == i: break
205
+ bnd.append(j)
206
+ boundaries.append(bnd)
207
+
208
+ return boundaries
@@ -0,0 +1,13 @@
1
+ ################################################################################
2
+ # #
3
+ # This file is part of the Morphomatics library #
4
+ # see https://github.com/morphomatics/morphomatics #
5
+ # #
6
+ # Copyright (C) 2024 Zuse Institute Berlin #
7
+ # #
8
+ # Morphomatics is distributed under the terms of the MIT License. #
9
+ # see $MORPHOMATICS/LICENSE #
10
+ # #
11
+ ################################################################################
12
+
13
+ from .operators import mfdg_laplace, mfdg_gradient, max_pooling, mean_pooling, in_degree_centrality, out_degree_centrality
@@ -0,0 +1,124 @@
1
+ ################################################################################
2
+ # #
3
+ # This file is part of the Morphomatics library #
4
+ # see https://github.com/morphomatics/morphomatics #
5
+ # #
6
+ # Copyright (C) 2024 Zuse Institute Berlin #
7
+ # #
8
+ # Morphomatics is distributed under the terms of the MIT License. #
9
+ # see $MORPHOMATICS/LICENSE #
10
+ # #
11
+ ################################################################################
12
+
13
+ import jax
14
+ from jax import tree_util as tree
15
+ import jax.numpy as jnp
16
+ import jraph
17
+
18
+ from morphomatics.manifold import Manifold
19
+
20
+
21
+ def mfdg_gradient(M: Manifold, graph: jraph.GraphsTuple) -> jnp.array:
22
+ """Given a graph with manifold-valued features returns the gradient as defined in https://arxiv.org/abs/1702.05293
23
+
24
+ :param M: feature manifold
25
+ :param graph: M-valued graph
26
+ :return: edge gradients
27
+ """
28
+
29
+ # gradients on directed edges are stored along first axis (orderd given by graph.senders/receivers)
30
+ logs = jax.vmap(M.connec.log)(graph.nodes[graph.senders], graph.nodes[graph.receivers])
31
+
32
+ return vec_times_kd(jnp.sqrt(graph.edges), logs)
33
+
34
+
35
+ def mfdg_laplace(M: Manifold, graph: jraph.GraphsTuple) -> jnp.array:
36
+ """Given a graph with manifold-valued features returns the isotropic graph 2-Laplacian as defined in
37
+ https://arxiv.org/abs/1702.05293
38
+
39
+ :param M: feature manifold
40
+ :param graph: M-valued graph
41
+ :return: Laplace vectors at node feature
42
+ """
43
+
44
+ # multiply negative gradients with corresponding square root edge weights
45
+ weighted_gradients = vec_times_kd(jnp.sqrt(graph.edges), -mfdg_gradient(M, graph))
46
+
47
+ return jax.ops.segment_sum(weighted_gradients, graph.senders, num_segments=len(graph.nodes)) # already ordered
48
+
49
+
50
+ def max_pooling(G: jraph.GraphsTuple, z: jnp.ndarray) -> jnp.array:
51
+ """Graph-wise max pooling of features z over graph G.
52
+
53
+ :param G: graph
54
+ :param z: scalar graph features
55
+ :return: max value for each graph in the batch
56
+ """
57
+ sum_n_node = tree.tree_leaves(G.nodes)[0].shape[0]
58
+ n_graph = G.n_node.shape[0]
59
+ graph_idx = jnp.arange(n_graph)
60
+
61
+ node_gr_idx = jnp.repeat(
62
+ graph_idx, G.n_node, axis=0, total_repeat_length=sum_n_node)
63
+
64
+ # return constant (not -inf) for graphs without edges as these can appear through batching
65
+ return jraph.segment_max_or_constant(z, node_gr_idx, n_graph, indices_are_sorted=True)
66
+
67
+
68
+ def mean_pooling(G: jraph.GraphsTuple, z: jnp.ndarray) -> jnp.array:
69
+ """Graph-wise mean pooling of features z over graph G.
70
+
71
+ :param G: graph
72
+ :param z: scalar graph features
73
+ :return: mean value for each graph in the batch
74
+ """
75
+ sum_n_node = tree.tree_leaves(G.nodes)[0].shape[0]
76
+ n_graph = G.n_node.shape[0]
77
+ graph_idx = jnp.arange(n_graph)
78
+
79
+ node_gr_idx = jnp.repeat(
80
+ graph_idx, G.n_node, axis=0, total_repeat_length=sum_n_node)
81
+
82
+ return jax.ops.segment_sum(z, node_gr_idx, n_graph, indices_are_sorted=True) / jnp.clip(G.n_node, 1).reshape((n_graph,) + (1,)*(z.ndim-1))
83
+
84
+
85
+ def in_degree_centrality(G: jraph.GraphsTuple) -> float:
86
+ """ Normalized in-degree centrality
87
+
88
+ """
89
+ def f(n: int):
90
+ return jnp.count_nonzero(G.receivers == n)
91
+
92
+ d = jax.vmap(f)(jnp.arange(G.n_node[0]))
93
+
94
+ return d / jnp.max(d)
95
+
96
+
97
+ def out_degree_centrality(G: jraph.GraphsTuple) -> float:
98
+ """ Normalized out-degree centrality
99
+
100
+ """
101
+ def f(n: int):
102
+ return jnp.count_nonzero(G.senders == n)
103
+
104
+ d = jax.vmap(f)(jnp.arange(G.n_node[0]))
105
+
106
+ return d / jnp.max(d)
107
+
108
+
109
+ def atleast_kd(array, k) -> jnp.array:
110
+ """Make array at least k-dimensional"""
111
+ array = jnp.asarray(array)
112
+ new_shape = array.shape + (1,) * (k - array.ndim)
113
+ return array.reshape(new_shape)
114
+
115
+
116
+ def vec_times_kd(vec: jnp.array, tensor: jnp.array) -> jnp.array:
117
+ """Multiply the i-th slice (along first axis) of a tensor with the i-th element of a given vector
118
+
119
+ The vector can be 2-dimensional as long as one of the dimensions is of size one, i.e, it is a proper vector.
120
+
121
+ Attention: non-jitable with asserts
122
+ """
123
+
124
+ return atleast_kd(vec, len(tensor.shape)) * tensor
@@ -0,0 +1,46 @@
1
+ ################################################################################
2
+ # #
3
+ # This file is part of the Morphomatics library #
4
+ # see https://github.com/morphomatics/morphomatics #
5
+ # #
6
+ # Copyright (C) 2024 Zuse Institute Berlin #
7
+ # #
8
+ # Morphomatics is distributed under the terms of the MIT License. #
9
+ # see $MORPHOMATICS/LICENSE #
10
+ # #
11
+ ################################################################################
12
+
13
+ # Abstract classes for manifolds
14
+ from .lie_group import LieGroup
15
+ from .connection import Connection
16
+ from .metric import Metric
17
+ from .manifold import Manifold
18
+
19
+ from. power_manifold import PowerManifold
20
+ from. product_manifold import ProductManifold
21
+
22
+ # Standard manifolds
23
+ from .euclidean import Euclidean
24
+ from .gl_p_n import GLpn
25
+ from .so_3 import SO3
26
+ from .se_3 import SE3
27
+ from .spd import SPD
28
+ from .sphere import Sphere
29
+ from .hyperbolic_space import HyperbolicSpace
30
+ from .grassmann import Grassmann
31
+ from .tangent_bundle import TangentBundle
32
+
33
+ # PyManopt Wrapper for manifolds
34
+ from .manopt_wrapper import ManoptWrap
35
+
36
+ # Shape spaces
37
+ from .shape_space import ShapeSpace
38
+ from .fundamental_coords import FundamentalCoords
39
+ from .differential_coords import DifferentialCoords
40
+ from .point_distribution_model import PointDistributionModel
41
+ from .gl_p_coords import GLpCoords
42
+ from .kendall import Kendall
43
+
44
+ # Space of shape trajectories
45
+ from .bezierfold import Bezierfold
46
+ from .cubic_bezierfold import CubicBezierfold