risk-network 0.0.3b3__py3-none-any.whl → 0.0.4__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.
risk/__init__.py CHANGED
@@ -2,12 +2,9 @@
2
2
  risk
3
3
  ~~~~
4
4
 
5
- risk
6
- ~~~~
7
-
8
5
  RISK: RISK Infers Spatial Kinship
9
6
  """
10
7
 
11
8
  from risk.risk import RISK
12
9
 
13
- __version__ = "0.0.3-beta.3"
10
+ __version__ = "0.0.4"
@@ -171,7 +171,9 @@ def get_description(words_column: pd.Series) -> str:
171
171
  stop_words = set(stopwords.words("english"))
172
172
  # Tokenize the concatenated string and filter out stopwords and non-alphabetic words
173
173
  words = [
174
- word.lower()
174
+ (
175
+ word.lower() if word.istitle() else word
176
+ ) # Lowercase all words except proper nouns (e.g., RNA, mRNA)
175
177
  for word in word_tokenize(words_column.str.cat(sep=" "))
176
178
  if word.isalpha() and word.lower() not in stop_words
177
179
  ]
@@ -195,7 +197,7 @@ def _simplify_word_list(words: List[str], threshold: float = 0.80) -> List[str]:
195
197
  word_counts = Counter(words)
196
198
  filtered_words = []
197
199
  used_words = set()
198
-
200
+ # Iterate through the words to find similar words
199
201
  for word in word_counts:
200
202
  if word in used_words:
201
203
  continue
risk/annotations/io.py CHANGED
@@ -25,7 +25,7 @@ class AnnotationsIO:
25
25
  def __init__(self):
26
26
  pass
27
27
 
28
- def load_json_annotations(self, filepath: str, network: nx.Graph) -> Dict[str, Any]:
28
+ def load_json_annotation(self, filepath: str, network: nx.Graph) -> Dict[str, Any]:
29
29
  """Load annotations from a JSON file and convert them to a DataFrame.
30
30
 
31
31
  Args:
@@ -170,7 +170,16 @@ def _impute_neighbors(
170
170
  # Calculate shortest distances for each node to determine the distance threshold
171
171
  shortest_distances = []
172
172
  for node in network.nodes():
173
- neighbors = [n for n in network.neighbors(node) if binary_enrichment_matrix[n].sum() != 0]
173
+ try:
174
+ neighbors = [
175
+ n for n in network.neighbors(node) if binary_enrichment_matrix[n].sum() != 0
176
+ ]
177
+ except IndexError as e:
178
+ raise IndexError(
179
+ f"Failed to find neighbors for node '{node}': Ensure that the node exists in the network and that the binary enrichment matrix is correctly indexed."
180
+ ) from e
181
+
182
+ # Calculate the shortest distance to a neighbor
174
183
  if neighbors:
175
184
  shortest_distance = min([_get_euclidean_distance(node, n, network) for n in neighbors])
176
185
  shortest_distances.append(shortest_distance)
@@ -312,7 +321,11 @@ def _calculate_threshold(average_distances: list, distance_threshold: float) ->
312
321
  rank_percentiles = np.linspace(0, 1, len(sorted_distances))
313
322
  # Interpolating the ranks to 1000 evenly spaced percentiles
314
323
  interpolated_percentiles = np.linspace(0, 1, 1000)
315
- smoothed_distances = np.interp(interpolated_percentiles, rank_percentiles, sorted_distances)
324
+ try:
325
+ smoothed_distances = np.interp(interpolated_percentiles, rank_percentiles, sorted_distances)
326
+ except ValueError as e:
327
+ raise ValueError("No significant annotations found.") from e
328
+
316
329
  # Determine the index corresponding to the distance threshold
317
330
  threshold_index = int(np.ceil(distance_threshold * len(smoothed_distances))) - 1
318
331
  # Return the smoothed distance at the calculated index
risk/network/geometry.py CHANGED
@@ -7,13 +7,13 @@ import networkx as nx
7
7
  import numpy as np
8
8
 
9
9
 
10
- def apply_edge_lengths(
10
+ def assign_edge_lengths(
11
11
  G: nx.Graph,
12
12
  compute_sphere: bool = True,
13
13
  surface_depth: float = 0.0,
14
14
  include_edge_weight: bool = False,
15
15
  ) -> nx.Graph:
16
- """Apply edge lengths in the graph, optionally mapping nodes to a sphere and including edge weights.
16
+ """Assign edge lengths in the graph, optionally mapping nodes to a sphere and including edge weights.
17
17
 
18
18
  Args:
19
19
  G (nx.Graph): The input graph.
risk/network/graph.py CHANGED
@@ -49,8 +49,8 @@ class NetworkGraph:
49
49
  self.trimmed_domains = trimmed_domains
50
50
  self.node_label_to_id_map = node_label_to_id_map
51
51
  self.node_enrichment_sums = node_enrichment_sums
52
- # NOTE: self.G and self.node_coordinates are declared in _initialize_network
53
- self.G = None
52
+ # NOTE: self.network and self.node_coordinates are declared in _initialize_network
53
+ self.network = None
54
54
  self.node_coordinates = None
55
55
  self._initialize_network(network)
56
56
 
@@ -95,8 +95,8 @@ class NetworkGraph:
95
95
  """
96
96
  # Unfold the network's 3D coordinates to 2D
97
97
  G_2d = _unfold_sphere_to_plane(G)
98
- # Assign the unfolded graph to self.G
99
- self.G = G_2d
98
+ # Assign the unfolded graph to self.network
99
+ self.network = G_2d
100
100
  # Extract 2D coordinates of nodes
101
101
  self.node_coordinates = _extract_node_coordinates(G_2d)
102
102
 
risk/network/io.py CHANGED
@@ -6,6 +6,7 @@ This file contains the code for the RISK class and command-line access.
6
6
  """
7
7
 
8
8
  import json
9
+ import os
9
10
  import pickle
10
11
  import shutil
11
12
  import zipfile
@@ -14,7 +15,7 @@ from xml.dom import minidom
14
15
  import networkx as nx
15
16
  import pandas as pd
16
17
 
17
- from risk.network.geometry import apply_edge_lengths
18
+ from risk.network.geometry import assign_edge_lengths
18
19
  from risk.log import params, print_header
19
20
 
20
21
 
@@ -29,25 +30,67 @@ class NetworkIO:
29
30
  self,
30
31
  compute_sphere: bool = True,
31
32
  surface_depth: float = 0.0,
32
- distance_metric: str = "dijkstra",
33
- edge_length_threshold: float = 0.5,
34
- louvain_resolution: float = 0.1,
35
33
  min_edges_per_node: int = 0,
36
34
  include_edge_weight: bool = True,
37
35
  weight_label: str = "weight",
38
36
  ):
37
+ """Initialize the NetworkIO class.
38
+
39
+ Args:
40
+ compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
41
+ surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
42
+ min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
43
+ include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
44
+ weight_label (str, optional): Label for edge weights. Defaults to "weight".
45
+ """
39
46
  self.compute_sphere = compute_sphere
40
47
  self.surface_depth = surface_depth
48
+ self.min_edges_per_node = min_edges_per_node
41
49
  self.include_edge_weight = include_edge_weight
42
50
  self.weight_label = weight_label
43
- self.distance_metric = distance_metric
44
- self.edge_length_threshold = edge_length_threshold
45
- self.louvain_resolution = louvain_resolution
46
- self.min_edges_per_node = min_edges_per_node
51
+ params.log_network(
52
+ compute_sphere=compute_sphere,
53
+ surface_depth=surface_depth,
54
+ min_edges_per_node=min_edges_per_node,
55
+ include_edge_weight=include_edge_weight,
56
+ weight_label=weight_label,
57
+ )
47
58
 
48
- def load_gpickle_network(self, filepath: str) -> nx.Graph:
59
+ @classmethod
60
+ def load_gpickle_network(
61
+ cls,
62
+ filepath: str,
63
+ compute_sphere: bool = True,
64
+ surface_depth: float = 0.0,
65
+ min_edges_per_node: int = 0,
66
+ include_edge_weight: bool = True,
67
+ weight_label: str = "weight",
68
+ ) -> nx.Graph:
49
69
  """Load a network from a GPickle file.
50
70
 
71
+ Args:
72
+ filepath (str): Path to the GPickle file.
73
+ compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
74
+ surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
75
+ min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
76
+ include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
77
+ weight_label (str, optional): Label for edge weights. Defaults to "weight".
78
+
79
+ Returns:
80
+ nx.Graph: Loaded and processed network.
81
+ """
82
+ networkio = cls(
83
+ compute_sphere=compute_sphere,
84
+ surface_depth=surface_depth,
85
+ min_edges_per_node=min_edges_per_node,
86
+ include_edge_weight=include_edge_weight,
87
+ weight_label=weight_label,
88
+ )
89
+ return networkio._load_gpickle_network(filepath=filepath)
90
+
91
+ def _load_gpickle_network(self, filepath: str) -> nx.Graph:
92
+ """Private method to load a network from a GPickle file.
93
+
51
94
  Args:
52
95
  filepath (str): Path to the GPickle file.
53
96
 
@@ -62,11 +105,43 @@ class NetworkIO:
62
105
 
63
106
  return self._initialize_graph(G)
64
107
 
65
- def load_networkx_network(self, G: nx.Graph) -> nx.Graph:
108
+ @classmethod
109
+ def load_networkx_network(
110
+ cls,
111
+ network: nx.Graph,
112
+ compute_sphere: bool = True,
113
+ surface_depth: float = 0.0,
114
+ min_edges_per_node: int = 0,
115
+ include_edge_weight: bool = True,
116
+ weight_label: str = "weight",
117
+ ) -> nx.Graph:
66
118
  """Load a NetworkX graph.
67
119
 
68
120
  Args:
69
- G (nx.Graph): A NetworkX graph object.
121
+ network (nx.Graph): A NetworkX graph object.
122
+ compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
123
+ surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
124
+ min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
125
+ include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
126
+ weight_label (str, optional): Label for edge weights. Defaults to "weight".
127
+
128
+ Returns:
129
+ nx.Graph: Loaded and processed network.
130
+ """
131
+ networkio = cls(
132
+ compute_sphere=compute_sphere,
133
+ surface_depth=surface_depth,
134
+ min_edges_per_node=min_edges_per_node,
135
+ include_edge_weight=include_edge_weight,
136
+ weight_label=weight_label,
137
+ )
138
+ return networkio._load_networkx_network(network=network)
139
+
140
+ def _load_networkx_network(self, network: nx.Graph) -> nx.Graph:
141
+ """Private method to load a NetworkX graph.
142
+
143
+ Args:
144
+ network (nx.Graph): A NetworkX graph object.
70
145
 
71
146
  Returns:
72
147
  nx.Graph: Processed network.
@@ -74,17 +149,60 @@ class NetworkIO:
74
149
  filetype = "NetworkX"
75
150
  params.log_network(filetype=filetype)
76
151
  self._log_loading(filetype)
77
- return self._initialize_graph(G)
152
+ return self._initialize_graph(network)
78
153
 
154
+ @classmethod
79
155
  def load_cytoscape_network(
80
- self,
156
+ cls,
81
157
  filepath: str,
82
158
  source_label: str = "source",
83
159
  target_label: str = "target",
160
+ compute_sphere: bool = True,
161
+ surface_depth: float = 0.0,
162
+ min_edges_per_node: int = 0,
163
+ include_edge_weight: bool = True,
164
+ weight_label: str = "weight",
84
165
  view_name: str = "",
85
166
  ) -> nx.Graph:
86
167
  """Load a network from a Cytoscape file.
87
168
 
169
+ Args:
170
+ filepath (str): Path to the Cytoscape file.
171
+ source_label (str, optional): Source node label. Defaults to "source".
172
+ target_label (str, optional): Target node label. Defaults to "target".
173
+ view_name (str, optional): Specific view name to load. Defaults to None.
174
+ compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
175
+ surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
176
+ min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
177
+ include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
178
+ weight_label (str, optional): Label for edge weights. Defaults to "weight".
179
+
180
+ Returns:
181
+ nx.Graph: Loaded and processed network.
182
+ """
183
+ networkio = cls(
184
+ compute_sphere=compute_sphere,
185
+ surface_depth=surface_depth,
186
+ min_edges_per_node=min_edges_per_node,
187
+ include_edge_weight=include_edge_weight,
188
+ weight_label=weight_label,
189
+ )
190
+ return networkio._load_cytoscape_network(
191
+ filepath=filepath,
192
+ source_label=source_label,
193
+ target_label=target_label,
194
+ view_name=view_name,
195
+ )
196
+
197
+ def _load_cytoscape_network(
198
+ self,
199
+ filepath: str,
200
+ source_label: str = "source",
201
+ target_label: str = "target",
202
+ view_name: str = "",
203
+ ) -> nx.Graph:
204
+ """Private method to load a network from a Cytoscape file.
205
+
88
206
  Args:
89
207
  filepath (str): Path to the Cytoscape file.
90
208
  source_label (str, optional): Source node label. Defaults to "source".
@@ -98,14 +216,20 @@ class NetworkIO:
98
216
  params.log_network(filetype=filetype, filepath=str(filepath))
99
217
  self._log_loading(filetype, filepath=filepath)
100
218
  cys_files = []
219
+ tmp_dir = ".tmp_cytoscape"
101
220
  # Try / finally to remove unzipped files
102
221
  try:
103
- # Unzip CYS file
222
+ # Create the temporary directory if it doesn't exist
223
+ if not os.path.exists(tmp_dir):
224
+ os.makedirs(tmp_dir)
225
+
226
+ # Unzip CYS file into the temporary directory
104
227
  with zipfile.ZipFile(filepath, "r") as zip_ref:
105
228
  cys_files = zip_ref.namelist()
106
- zip_ref.extractall("./")
229
+ zip_ref.extractall(tmp_dir)
230
+
107
231
  # Get first view and network instances
108
- cys_view_files = [cf for cf in cys_files if "/views/" in cf]
232
+ cys_view_files = [os.path.join(tmp_dir, cf) for cf in cys_files if "/views/" in cf]
109
233
  cys_view_file = (
110
234
  cys_view_files[0]
111
235
  if not view_name
@@ -127,7 +251,7 @@ class NetworkIO:
127
251
  # Read the node attributes (from /tables/)
128
252
  attribute_metadata_keywords = ["/tables/", "SHARED_ATTRS", "edge.cytable"]
129
253
  attribute_metadata = [
130
- cf
254
+ os.path.join(tmp_dir, cf)
131
255
  for cf in cys_files
132
256
  if all(keyword in cf for keyword in attribute_metadata_keywords)
133
257
  ][0]
@@ -174,14 +298,53 @@ class NetworkIO:
174
298
  return self._initialize_graph(G)
175
299
 
176
300
  finally:
177
- # Remove unzipped files/directories
178
- cys_dirnames = list(set([cf.split("/")[0] for cf in cys_files]))
179
- for dirname in cys_dirnames:
180
- shutil.rmtree(dirname)
301
+ # Remove the temporary directory and its contents
302
+ if os.path.exists(tmp_dir):
303
+ shutil.rmtree(tmp_dir)
181
304
 
182
- def load_cytoscape_json_network(self, filepath, source_label="source", target_label="target"):
305
+ @classmethod
306
+ def load_cytoscape_json_network(
307
+ cls,
308
+ filepath: str,
309
+ source_label: str = "source",
310
+ target_label: str = "target",
311
+ compute_sphere: bool = True,
312
+ surface_depth: float = 0.0,
313
+ min_edges_per_node: int = 0,
314
+ include_edge_weight: bool = True,
315
+ weight_label: str = "weight",
316
+ ) -> nx.Graph:
183
317
  """Load a network from a Cytoscape JSON (.cyjs) file.
184
318
 
319
+ Args:
320
+ filepath (str): Path to the Cytoscape JSON file.
321
+ source_label (str, optional): Source node label. Default is "source".
322
+ target_label (str, optional): Target node label. Default is "target".
323
+ compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
324
+ surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
325
+ min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
326
+ include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
327
+ weight_label (str, optional): Label for edge weights. Defaults to "weight".
328
+
329
+ Returns:
330
+ NetworkX graph: Loaded and processed network.
331
+ """
332
+ networkio = cls(
333
+ compute_sphere=compute_sphere,
334
+ surface_depth=surface_depth,
335
+ min_edges_per_node=min_edges_per_node,
336
+ include_edge_weight=include_edge_weight,
337
+ weight_label=weight_label,
338
+ )
339
+ return networkio._load_cytoscape_json_network(
340
+ filepath=filepath,
341
+ source_label=source_label,
342
+ target_label=target_label,
343
+ )
344
+
345
+ def _load_cytoscape_json_network(self, filepath, source_label="source", target_label="target"):
346
+ """Private method to load a network from a Cytoscape JSON (.cyjs) file.
347
+
185
348
  Args:
186
349
  filepath (str): Path to the Cytoscape JSON file.
187
350
  source_label (str, optional): Source node label. Default is "source".
@@ -193,36 +356,46 @@ class NetworkIO:
193
356
  filetype = "Cytoscape JSON"
194
357
  params.log_network(filetype=filetype, filepath=str(filepath))
195
358
  self._log_loading(filetype, filepath=filepath)
359
+
196
360
  # Load the Cytoscape JSON file
197
361
  with open(filepath, "r") as f:
198
362
  cyjs_data = json.load(f)
199
363
 
200
364
  # Create a graph
201
365
  G = nx.Graph()
202
- # Process nodes
366
+ # Store node positions for later use
203
367
  node_x_positions = {}
204
368
  node_y_positions = {}
205
369
  for node in cyjs_data["elements"]["nodes"]:
206
370
  node_data = node["data"]
207
- node_id = node_data["id"]
371
+ node_id = node_data["id_original"]
208
372
  node_x_positions[node_id] = node["position"]["x"]
209
373
  node_y_positions[node_id] = node["position"]["y"]
210
- G.add_node(node_id)
211
- G.nodes[node_id]["label"] = node_data.get("name", node_id)
212
- G.nodes[node_id]["x"] = node["position"]["x"]
213
- G.nodes[node_id]["y"] = node["position"]["y"]
214
374
 
215
- # Process edges
375
+ # Process edges and add them to the graph
216
376
  for edge in cyjs_data["elements"]["edges"]:
217
377
  edge_data = edge["data"]
218
- source = edge_data[source_label]
219
- target = edge_data[target_label]
378
+ source = edge_data[f"{source_label}_original"]
379
+ target = edge_data[f"{target_label}_original"]
380
+ # Add the edge to the graph, optionally including weights
220
381
  if self.weight_label is not None and self.weight_label in edge_data:
221
382
  weight = float(edge_data[self.weight_label])
222
383
  G.add_edge(source, target, weight=weight)
223
384
  else:
224
385
  G.add_edge(source, target)
225
386
 
387
+ # Ensure nodes exist in the graph and add them if not present
388
+ if source not in G:
389
+ G.add_node(source)
390
+ if target not in G:
391
+ G.add_node(target)
392
+
393
+ # Add node attributes (like label, x, y positions)
394
+ for node in G.nodes():
395
+ G.nodes[node]["label"] = node
396
+ G.nodes[node]["x"] = node_x_positions.get(node, 0) # Use stored positions
397
+ G.nodes[node]["y"] = node_y_positions.get(node, 0) # Use stored positions
398
+
226
399
  # Initialize the graph
227
400
  return self._initialize_graph(G)
228
401
 
@@ -235,12 +408,13 @@ class NetworkIO:
235
408
  Returns:
236
409
  nx.Graph: The processed and validated graph.
237
410
  """
411
+ self._validate_nodes(G)
412
+ self._assign_edge_weights(G)
413
+ self._assign_edge_lengths(G)
414
+ self._remove_invalid_graph_properties(G)
238
415
  # IMPORTANT: This is where the graph node labels are converted to integers
416
+ # Make sure to perform this step after all other processing
239
417
  G = nx.relabel_nodes(G, {node: idx for idx, node in enumerate(G.nodes)})
240
- self._remove_invalid_graph_properties(G)
241
- self._validate_edges(G)
242
- self._validate_nodes(G)
243
- self._process_graph(G)
244
418
  return G
245
419
 
246
420
  def _remove_invalid_graph_properties(self, G: nx.Graph) -> None:
@@ -249,18 +423,26 @@ class NetworkIO:
249
423
  Args:
250
424
  G (nx.Graph): A NetworkX graph object.
251
425
  """
252
- print(f"Minimum edges per node: {self.min_edges_per_node}")
253
- # Remove nodes with fewer edges than the specified threshold
254
- nodes_with_few_edges = [
255
- node for node in G.nodes() if G.degree(node) <= self.min_edges_per_node
256
- ]
257
- G.remove_nodes_from(nodes_with_few_edges)
258
- # Remove self-loop edges
259
- self_loops = list(nx.selfloop_edges(G))
260
- G.remove_edges_from(self_loops)
261
-
262
- def _validate_edges(self, G: nx.Graph) -> None:
263
- """Validate and assign weights to the edges in the graph.
426
+ # First, Remove self-loop edges to ensure correct edge count
427
+ G.remove_edges_from(list(nx.selfloop_edges(G)))
428
+ # Then, iteratively remove nodes with fewer edges than the specified threshold
429
+ while True:
430
+ nodes_to_remove = [
431
+ node for node in G.nodes() if G.degree(node) < self.min_edges_per_node
432
+ ]
433
+ if not nodes_to_remove:
434
+ break # Exit loop if no more nodes to remove
435
+
436
+ # Remove the nodes and their associated edges
437
+ G.remove_nodes_from(nodes_to_remove)
438
+
439
+ # Optionally: Remove any isolated nodes if needed
440
+ isolated_nodes = list(nx.isolates(G))
441
+ if isolated_nodes:
442
+ G.remove_nodes_from(isolated_nodes)
443
+
444
+ def _assign_edge_weights(self, G: nx.Graph) -> None:
445
+ """Assign weights to the edges in the graph.
264
446
 
265
447
  Args:
266
448
  G (nx.Graph): A NetworkX graph object.
@@ -289,13 +471,13 @@ class NetworkIO:
289
471
  ), f"Node {node} is missing 'x' or 'y' position attributes."
290
472
  assert "label" in attrs, f"Node {node} is missing a 'label' attribute."
291
473
 
292
- def _process_graph(self, G: nx.Graph) -> None:
474
+ def _assign_edge_lengths(self, G: nx.Graph) -> None:
293
475
  """Prepare the network by adjusting surface depth and calculating edge lengths.
294
476
 
295
477
  Args:
296
478
  G (nx.Graph): The input network graph.
297
479
  """
298
- apply_edge_lengths(
480
+ assign_edge_lengths(
299
481
  G,
300
482
  compute_sphere=self.compute_sphere,
301
483
  surface_depth=self.surface_depth,
@@ -317,10 +499,9 @@ class NetworkIO:
317
499
  print(f"Filetype: {filetype}")
318
500
  if filepath:
319
501
  print(f"Filepath: {filepath}")
320
- print(f"Projection: {'Sphere' if self.compute_sphere else 'Plane'}")
321
- if self.compute_sphere:
322
- print(f"Surface depth: {self.surface_depth}")
323
- print(f"Edge length threshold: {self.edge_length_threshold}")
324
502
  print(f"Edge weight: {'Included' if self.include_edge_weight else 'Excluded'}")
325
503
  if self.include_edge_weight:
326
504
  print(f"Weight label: {self.weight_label}")
505
+ print(f"Projection: {'Sphere' if self.compute_sphere else 'Plane'}")
506
+ if self.compute_sphere:
507
+ print(f"Surface depth: {self.surface_depth}")