clabtoolkit 0.3.3__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.
- clabtoolkit/__init__.py +5 -0
- clabtoolkit/bidstools.py +2260 -0
- clabtoolkit/config/bids.json +147 -0
- clabtoolkit/config/config.json +58 -0
- clabtoolkit/config/lobes.json +112 -0
- clabtoolkit/config/stats_mapping.json +140 -0
- clabtoolkit/config/viz_views.json +268 -0
- clabtoolkit/connectivitytools.py +0 -0
- clabtoolkit/dicomtools.py +941 -0
- clabtoolkit/dwitools.py +1999 -0
- clabtoolkit/freesurfertools.py +5378 -0
- clabtoolkit/imagetools.py +2734 -0
- clabtoolkit/misctools.py +5204 -0
- clabtoolkit/morphometrytools.py +2970 -0
- clabtoolkit/networktools.py +351 -0
- clabtoolkit/parcellationtools.py +3239 -0
- clabtoolkit/pipelinetools.py +1187 -0
- clabtoolkit/plottools.py +682 -0
- clabtoolkit/qcqatools.py +1305 -0
- clabtoolkit/segmentationtools.py +356 -0
- clabtoolkit/surfacetools.py +2785 -0
- clabtoolkit/visualizationtools.py +5575 -0
- clabtoolkit-0.3.3.dist-info/METADATA +212 -0
- clabtoolkit-0.3.3.dist-info/RECORD +28 -0
- clabtoolkit-0.3.3.dist-info/WHEEL +5 -0
- clabtoolkit-0.3.3.dist-info/licenses/AUTHORS.rst +13 -0
- clabtoolkit-0.3.3.dist-info/licenses/LICENSE +16 -0
- clabtoolkit-0.3.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.sparse import csr_matrix
|
|
3
|
+
from typing import Tuple, Union, Optional
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
####################################################################################################
|
|
8
|
+
####################################################################################################
|
|
9
|
+
############ ############
|
|
10
|
+
############ ############
|
|
11
|
+
############ Section 1: Methods dedicated create CSR graphs from different sources ############
|
|
12
|
+
############ CSR (Compressed Sparse Row) format is efficient for graph representation ############
|
|
13
|
+
############ and is widely used in scientific computing and machine learning. ############
|
|
14
|
+
############ ############
|
|
15
|
+
############ ############
|
|
16
|
+
####################################################################################################
|
|
17
|
+
####################################################################################################
|
|
18
|
+
def adjacency_matrix_to_csr(adj_matrix: np.ndarray) -> csr_matrix:
|
|
19
|
+
"""
|
|
20
|
+
Convert a dense adjacency matrix to CSR (Compressed Sparse Row) format.
|
|
21
|
+
|
|
22
|
+
This method takes a square adjacency matrix where non-zero entries represent
|
|
23
|
+
connections between vertices and converts it to an efficient sparse representation.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
adj_matrix : np.ndarray
|
|
28
|
+
A square 2D numpy array representing the adjacency matrix.
|
|
29
|
+
Shape should be (n_vertices, n_vertices) where n_vertices is the number
|
|
30
|
+
of vertices in the graph. Non-zero values represent edge weights.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
csr_matrix
|
|
35
|
+
A scipy sparse CSR matrix representing the same graph connectivity.
|
|
36
|
+
|
|
37
|
+
Raises
|
|
38
|
+
------
|
|
39
|
+
ValueError
|
|
40
|
+
If the input matrix is not 2D or not square.
|
|
41
|
+
TypeError
|
|
42
|
+
If the input is not a numpy array.
|
|
43
|
+
|
|
44
|
+
Examples
|
|
45
|
+
--------
|
|
46
|
+
>>> import numpy as np
|
|
47
|
+
>>> # Create a simple 4-vertex graph
|
|
48
|
+
>>> adj = np.array([[0, 1, 1, 0],
|
|
49
|
+
... [1, 0, 1, 1],
|
|
50
|
+
... [1, 1, 0, 1],
|
|
51
|
+
... [0, 1, 1, 0]])
|
|
52
|
+
>>> csr_graph = adjacency_matrix_to_csr(adj)
|
|
53
|
+
>>> print(csr_graph.toarray())
|
|
54
|
+
[[0 1 1 0]
|
|
55
|
+
[1 0 1 1]
|
|
56
|
+
[1 1 0 1]
|
|
57
|
+
[0 1 1 0]]
|
|
58
|
+
|
|
59
|
+
>>> # With weighted edges
|
|
60
|
+
>>> adj_weighted = np.array([[0, 2.5, 1.0, 0],
|
|
61
|
+
... [2.5, 0, 0, 3.2],
|
|
62
|
+
... [1.0, 0, 0, 1.8],
|
|
63
|
+
... [0, 3.2, 1.8, 0]])
|
|
64
|
+
>>> csr_weighted = adjacency_matrix_to_csr(adj_weighted)
|
|
65
|
+
>>> print(f"Non-zero values: {csr_weighted.data}")
|
|
66
|
+
Non-zero values: [2.5 1. 2.5 3.2 1. 1.8 3.2 1.8]
|
|
67
|
+
"""
|
|
68
|
+
if not isinstance(adj_matrix, np.ndarray):
|
|
69
|
+
raise TypeError("Input must be a numpy array")
|
|
70
|
+
|
|
71
|
+
if adj_matrix.ndim != 2:
|
|
72
|
+
raise ValueError("Input must be a 2D array")
|
|
73
|
+
|
|
74
|
+
if adj_matrix.shape[0] != adj_matrix.shape[1]:
|
|
75
|
+
raise ValueError("Adjacency matrix must be square")
|
|
76
|
+
|
|
77
|
+
return csr_matrix(adj_matrix)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
####################################################################################################
|
|
81
|
+
def triangulated_mesh_to_csr(
|
|
82
|
+
faces: np.ndarray, n_vertices: Optional[int] = None
|
|
83
|
+
) -> csr_matrix:
|
|
84
|
+
"""
|
|
85
|
+
Convert triangulated mesh faces to a CSR graph representation.
|
|
86
|
+
|
|
87
|
+
This method constructs a graph where vertices are connected if they share
|
|
88
|
+
an edge in any triangle face. All edge weights are set to 1. The resulting
|
|
89
|
+
graph represents the 1-ring neighborhood connectivity of the mesh.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
faces : np.ndarray
|
|
94
|
+
A 2D numpy array of shape (n_faces, 3) where each row contains the
|
|
95
|
+
indices of three vertices forming a triangle. Vertex indices should
|
|
96
|
+
be non-negative integers.
|
|
97
|
+
|
|
98
|
+
n_vertices : int, optional
|
|
99
|
+
Total number of vertices in the mesh. If None, it will be inferred
|
|
100
|
+
as the maximum vertex index + 1. Providing this parameter is recommended
|
|
101
|
+
for meshes with isolated vertices.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
csr_matrix
|
|
106
|
+
A scipy sparse CSR matrix of shape (n_vertices, n_vertices) where
|
|
107
|
+
entry (i,j) is 1 if vertices i and j are connected by an edge in the mesh,
|
|
108
|
+
and 0 otherwise. The matrix is symmetric for undirected graphs.
|
|
109
|
+
|
|
110
|
+
Raises
|
|
111
|
+
------
|
|
112
|
+
ValueError
|
|
113
|
+
If faces array is not 2D, doesn't have 3 columns, contains negative
|
|
114
|
+
indices, or if n_vertices is less than the maximum vertex index.
|
|
115
|
+
|
|
116
|
+
TypeError
|
|
117
|
+
If faces is not a numpy array or contains non-integer values.
|
|
118
|
+
|
|
119
|
+
Examples
|
|
120
|
+
--------
|
|
121
|
+
>>> import numpy as np
|
|
122
|
+
>>> # Define a simple tetrahedron (4 faces, 4 vertices)
|
|
123
|
+
>>> faces = np.array([[0, 1, 2],
|
|
124
|
+
... [0, 1, 3],
|
|
125
|
+
... [0, 2, 3],
|
|
126
|
+
... [1, 2, 3]])
|
|
127
|
+
>>> csr_graph = triangulated_mesh_to_csr(faces)
|
|
128
|
+
>>> print("Adjacency matrix:")
|
|
129
|
+
>>> print(csr_graph.toarray())
|
|
130
|
+
Adjacency matrix:
|
|
131
|
+
[[0 1 1 1]
|
|
132
|
+
[1 0 1 1]
|
|
133
|
+
[1 1 0 1]
|
|
134
|
+
[1 1 1 0]]
|
|
135
|
+
|
|
136
|
+
>>> # Triangle mesh with explicit vertex count
|
|
137
|
+
>>> faces_triangle = np.array([[0, 1, 2]])
|
|
138
|
+
>>> csr_triangle = triangulated_mesh_to_csr(faces_triangle, n_vertices=5)
|
|
139
|
+
>>> print(f"Shape: {csr_triangle.shape}")
|
|
140
|
+
>>> print("Connections for triangle [0,1,2]:")
|
|
141
|
+
>>> print(csr_triangle.toarray())
|
|
142
|
+
Shape: (5, 5)
|
|
143
|
+
Connections for triangle [0,1,2]:
|
|
144
|
+
[[0 1 1 0 0]
|
|
145
|
+
[1 0 1 0 0]
|
|
146
|
+
[1 1 0 0 0]
|
|
147
|
+
[0 0 0 0 0]
|
|
148
|
+
[0 0 0 0 0]]
|
|
149
|
+
"""
|
|
150
|
+
if not isinstance(faces, np.ndarray):
|
|
151
|
+
raise TypeError("Faces must be a numpy array")
|
|
152
|
+
|
|
153
|
+
if faces.ndim != 2:
|
|
154
|
+
raise ValueError("Faces array must be 2D")
|
|
155
|
+
|
|
156
|
+
if faces.shape[1] != 3:
|
|
157
|
+
raise ValueError(
|
|
158
|
+
"Faces array must have exactly 3 columns for triangulated mesh"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if not np.issubdtype(faces.dtype, np.integer):
|
|
162
|
+
raise TypeError("Faces array must contain integer vertex indices")
|
|
163
|
+
|
|
164
|
+
if np.any(faces < 0):
|
|
165
|
+
raise ValueError("Vertex indices must be non-negative")
|
|
166
|
+
|
|
167
|
+
max_vertex_idx = np.max(faces)
|
|
168
|
+
|
|
169
|
+
if n_vertices is None:
|
|
170
|
+
n_vertices = max_vertex_idx + 1
|
|
171
|
+
elif n_vertices <= max_vertex_idx:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"n_vertices ({n_vertices}) must be greater than maximum vertex index ({max_vertex_idx})"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Extract all edges from triangular faces
|
|
177
|
+
# Each triangle (v0, v1, v2) generates edges: (v0,v1), (v1,v2), (v0,v2)
|
|
178
|
+
edges = []
|
|
179
|
+
for face in faces:
|
|
180
|
+
v0, v1, v2 = face
|
|
181
|
+
edges.extend([(v0, v1), (v1, v2), (v0, v2)])
|
|
182
|
+
|
|
183
|
+
edges = np.array(edges)
|
|
184
|
+
|
|
185
|
+
# Create symmetric edges (undirected graph)
|
|
186
|
+
edges_symmetric = np.vstack([edges, edges[:, [1, 0]]])
|
|
187
|
+
|
|
188
|
+
# Remove duplicate edges and create CSR matrix
|
|
189
|
+
row_indices = edges_symmetric[:, 0]
|
|
190
|
+
col_indices = edges_symmetric[:, 1]
|
|
191
|
+
data = np.ones(len(edges_symmetric), dtype=int)
|
|
192
|
+
|
|
193
|
+
# Create sparse matrix and eliminate duplicates by summing
|
|
194
|
+
csr_graph = csr_matrix(
|
|
195
|
+
(data, (row_indices, col_indices)), shape=(n_vertices, n_vertices)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Convert to binary (in case of duplicate edges)
|
|
199
|
+
csr_graph.data = (csr_graph.data > 0).astype(int)
|
|
200
|
+
|
|
201
|
+
return csr_graph
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
####################################################################################################
|
|
205
|
+
def edges_to_csr(
|
|
206
|
+
edges: np.ndarray,
|
|
207
|
+
edge_values: np.ndarray,
|
|
208
|
+
n_vertices: Optional[int] = None,
|
|
209
|
+
symmetric: bool = True,
|
|
210
|
+
) -> csr_matrix:
|
|
211
|
+
"""
|
|
212
|
+
Convert an edge list with values to CSR graph representation.
|
|
213
|
+
|
|
214
|
+
This method constructs a graph from a list of edges and their corresponding
|
|
215
|
+
weights/values. Useful for creating graphs from pre-computed edge lists.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
edges : np.ndarray
|
|
220
|
+
A 2D numpy array of shape (n_edges, 2) where each row contains the
|
|
221
|
+
indices of two connected vertices. Vertex indices should be non-negative integers.
|
|
222
|
+
|
|
223
|
+
edge_values : np.ndarray
|
|
224
|
+
A 1D numpy array of length n_edges containing the weight/value for each edge.
|
|
225
|
+
Values can be any numeric type (int, float, etc.).
|
|
226
|
+
|
|
227
|
+
n_vertices : int, optional
|
|
228
|
+
Total number of vertices in the graph. If None, it will be inferred
|
|
229
|
+
as the maximum vertex index + 1. Providing this parameter is recommended
|
|
230
|
+
for graphs with isolated vertices.
|
|
231
|
+
|
|
232
|
+
symmetric : bool, default=True
|
|
233
|
+
If True, creates an undirected graph by adding reverse edges with the same values.
|
|
234
|
+
If False, creates a directed graph using only the provided edges.
|
|
235
|
+
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
csr_matrix
|
|
239
|
+
A scipy sparse CSR matrix of shape (n_vertices, n_vertices) where
|
|
240
|
+
entry (i,j) contains the weight of the edge from vertex i to vertex j.
|
|
241
|
+
For undirected graphs (symmetric=True), the matrix is symmetric.
|
|
242
|
+
|
|
243
|
+
Raises
|
|
244
|
+
------
|
|
245
|
+
ValueError
|
|
246
|
+
If edges array is not 2D, doesn't have 2 columns, contains negative indices,
|
|
247
|
+
edge_values length doesn't match number of edges, or if n_vertices is less
|
|
248
|
+
than the maximum vertex index.
|
|
249
|
+
|
|
250
|
+
TypeError
|
|
251
|
+
If edges contains non-integer values or edge_values is not numeric.
|
|
252
|
+
|
|
253
|
+
Examples
|
|
254
|
+
--------
|
|
255
|
+
>>> import numpy as np
|
|
256
|
+
>>> # Create a simple weighted graph
|
|
257
|
+
>>> edges = np.array([[0, 1],
|
|
258
|
+
... [1, 2],
|
|
259
|
+
... [0, 2]])
|
|
260
|
+
>>> values = np.array([2.5, 1.0, 3.2])
|
|
261
|
+
>>> csr_graph = edges_to_csr(edges, values)
|
|
262
|
+
>>> print("Symmetric weighted graph:")
|
|
263
|
+
>>> print(csr_graph.toarray())
|
|
264
|
+
Symmetric weighted graph:
|
|
265
|
+
[[0. 2.5 3.2]
|
|
266
|
+
[2.5 0. 1. ]
|
|
267
|
+
[3.2 1. 0. ]]
|
|
268
|
+
|
|
269
|
+
>>> # Directed graph example
|
|
270
|
+
>>> edges_directed = np.array([[0, 1], [1, 2]])
|
|
271
|
+
>>> values_directed = np.array([0.8, 1.5])
|
|
272
|
+
>>> csr_directed = edges_to_csr(edges_directed, values_directed, symmetric=False)
|
|
273
|
+
>>> print("Directed graph:")
|
|
274
|
+
>>> print(csr_directed.toarray())
|
|
275
|
+
Directed graph:
|
|
276
|
+
[[0. 0.8 0. ]
|
|
277
|
+
[0. 0. 1.5]
|
|
278
|
+
[0. 0. 0. ]]
|
|
279
|
+
|
|
280
|
+
>>> # Handle duplicate edges (values are summed)
|
|
281
|
+
>>> edges_dup = np.array([[0, 1], [0, 1], [1, 0]])
|
|
282
|
+
>>> values_dup = np.array([1.0, 2.0, 0.5])
|
|
283
|
+
>>> csr_dup = edges_to_csr(edges_dup, values_dup)
|
|
284
|
+
>>> print("Duplicate edges (summed):")
|
|
285
|
+
>>> print(csr_dup.toarray())
|
|
286
|
+
Duplicate edges (summed):
|
|
287
|
+
[[0. 3.5]
|
|
288
|
+
[3.5 0. ]]
|
|
289
|
+
"""
|
|
290
|
+
if not isinstance(edges, np.ndarray):
|
|
291
|
+
raise TypeError("Edges must be a numpy array")
|
|
292
|
+
|
|
293
|
+
if not isinstance(edge_values, np.ndarray):
|
|
294
|
+
raise TypeError("Edge values must be a numpy array")
|
|
295
|
+
|
|
296
|
+
if edges.ndim != 2:
|
|
297
|
+
raise ValueError("Edges array must be 2D")
|
|
298
|
+
|
|
299
|
+
if edges.shape[1] != 2:
|
|
300
|
+
raise ValueError("Edges array must have exactly 2 columns")
|
|
301
|
+
|
|
302
|
+
if edge_values.ndim != 1:
|
|
303
|
+
raise ValueError("Edge values must be a 1D array")
|
|
304
|
+
|
|
305
|
+
if len(edges) != len(edge_values):
|
|
306
|
+
raise ValueError(
|
|
307
|
+
f"Number of edges ({len(edges)}) must match number of edge values ({len(edge_values)})"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
if not np.issubdtype(edges.dtype, np.integer):
|
|
311
|
+
raise TypeError("Edges array must contain integer vertex indices")
|
|
312
|
+
|
|
313
|
+
if not np.issubdtype(edge_values.dtype, np.number):
|
|
314
|
+
raise TypeError("Edge values must be numeric")
|
|
315
|
+
|
|
316
|
+
if np.any(edges < 0):
|
|
317
|
+
raise ValueError("Vertex indices must be non-negative")
|
|
318
|
+
|
|
319
|
+
if len(edges) == 0:
|
|
320
|
+
warnings.warn("Empty edge list provided", UserWarning)
|
|
321
|
+
if n_vertices is None:
|
|
322
|
+
n_vertices = 0
|
|
323
|
+
return csr_matrix((n_vertices, n_vertices))
|
|
324
|
+
|
|
325
|
+
max_vertex_idx = np.max(edges)
|
|
326
|
+
|
|
327
|
+
if n_vertices is None:
|
|
328
|
+
n_vertices = max_vertex_idx + 1
|
|
329
|
+
elif n_vertices <= max_vertex_idx:
|
|
330
|
+
raise ValueError(
|
|
331
|
+
f"n_vertices ({n_vertices}) must be greater than maximum vertex index ({max_vertex_idx})"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Prepare edge data
|
|
335
|
+
if symmetric:
|
|
336
|
+
# Add reverse edges for undirected graph
|
|
337
|
+
all_edges = np.vstack([edges, edges[:, [1, 0]]])
|
|
338
|
+
all_values = np.concatenate([edge_values, edge_values])
|
|
339
|
+
else:
|
|
340
|
+
all_edges = edges
|
|
341
|
+
all_values = edge_values
|
|
342
|
+
|
|
343
|
+
row_indices = all_edges[:, 0]
|
|
344
|
+
col_indices = all_edges[:, 1]
|
|
345
|
+
|
|
346
|
+
# Create CSR matrix (duplicate edges will be summed automatically)
|
|
347
|
+
csr_graph = csr_matrix(
|
|
348
|
+
(all_values, (row_indices, col_indices)), shape=(n_vertices, n_vertices)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return csr_graph
|