risk-network 0.0.11__py3-none-any.whl → 0.0.12b1__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 +1 -1
- risk/annotations/__init__.py +4 -1
- risk/annotations/io.py +48 -47
- risk/annotations/nltk_setup.py +2 -1
- risk/log/__init__.py +1 -1
- risk/log/parameters.py +21 -22
- risk/neighborhoods/__init__.py +0 -1
- risk/neighborhoods/api.py +2 -2
- risk/neighborhoods/community.py +33 -4
- risk/neighborhoods/domains.py +6 -4
- risk/neighborhoods/neighborhoods.py +7 -1
- risk/neighborhoods/stats/__init__.py +13 -0
- risk/neighborhoods/stats/permutation/__init__.py +6 -0
- risk/{stats → neighborhoods/stats}/permutation/permutation.py +7 -4
- risk/{stats → neighborhoods/stats}/permutation/test_functions.py +2 -2
- risk/{stats/stat_tests.py → neighborhoods/stats/tests.py} +21 -13
- risk/network/__init__.py +0 -2
- risk/network/graph/__init__.py +0 -2
- risk/network/graph/api.py +2 -2
- risk/network/graph/graph.py +56 -57
- risk/{stats/significance.py → network/graph/stats.py} +2 -2
- risk/network/graph/summary.py +2 -3
- risk/network/io.py +151 -8
- risk/network/plotter/__init__.py +0 -2
- risk/network/plotter/api.py +1 -1
- risk/network/plotter/canvas.py +35 -35
- risk/network/plotter/contour.py +11 -12
- risk/network/plotter/labels.py +257 -246
- risk/network/plotter/plotter.py +2 -4
- risk/network/plotter/utils/colors.py +3 -0
- risk/risk.py +5 -5
- risk_network-0.0.12b1.dist-info/METADATA +122 -0
- risk_network-0.0.12b1.dist-info/RECORD +40 -0
- {risk_network-0.0.11.dist-info → risk_network-0.0.12b1.dist-info}/WHEEL +1 -1
- risk/network/geometry.py +0 -150
- risk/stats/__init__.py +0 -15
- risk/stats/permutation/__init__.py +0 -6
- risk_network-0.0.11.dist-info/METADATA +0 -798
- risk_network-0.0.11.dist-info/RECORD +0 -41
- {risk_network-0.0.11.dist-info → risk_network-0.0.12b1.dist-info/licenses}/LICENSE +0 -0
- {risk_network-0.0.11.dist-info → risk_network-0.0.12b1.dist-info}/top_level.txt +0 -0
risk/network/graph/graph.py
CHANGED
@@ -65,8 +65,8 @@ class Graph:
|
|
65
65
|
# NOTE: Below this point, instance attributes (i.e., self) will be used!
|
66
66
|
self.domain_id_to_node_labels_map = self._create_domain_id_to_node_labels_map()
|
67
67
|
# Unfold the network's 3D coordinates to 2D and extract node coordinates
|
68
|
-
self.network = _unfold_sphere_to_plane(network)
|
69
|
-
self.node_coordinates = _extract_node_coordinates(self.network)
|
68
|
+
self.network = self._unfold_sphere_to_plane(network)
|
69
|
+
self.node_coordinates = self._extract_node_coordinates(self.network)
|
70
70
|
|
71
71
|
# NOTE: Only after the above attributes are initialized, we can create the summary
|
72
72
|
self.summary = Summary(annotations, neighborhoods, self)
|
@@ -97,8 +97,7 @@ class Graph:
|
|
97
97
|
domain_info["domains"].remove(domain_id)
|
98
98
|
domain_info["significances"].pop(domain_id)
|
99
99
|
|
100
|
-
|
101
|
-
def _create_domain_id_to_node_ids_map(domains: pd.DataFrame) -> Dict[int, Any]:
|
100
|
+
def _create_domain_id_to_node_ids_map(self, domains: pd.DataFrame) -> Dict[int, Any]:
|
102
101
|
"""Create a mapping from domains to the list of node IDs belonging to each domain.
|
103
102
|
|
104
103
|
Args:
|
@@ -115,8 +114,9 @@ class Graph:
|
|
115
114
|
|
116
115
|
return domain_id_to_node_ids_map
|
117
116
|
|
118
|
-
|
119
|
-
|
117
|
+
def _create_domain_id_to_domain_terms_map(
|
118
|
+
self, trimmed_domains: pd.DataFrame
|
119
|
+
) -> Dict[int, Any]:
|
120
120
|
"""Create a mapping from domain IDs to their corresponding terms.
|
121
121
|
|
122
122
|
Args:
|
@@ -132,8 +132,8 @@ class Graph:
|
|
132
132
|
)
|
133
133
|
)
|
134
134
|
|
135
|
-
@staticmethod
|
136
135
|
def _create_domain_id_to_domain_info_map(
|
136
|
+
self,
|
137
137
|
trimmed_domains: pd.DataFrame,
|
138
138
|
) -> Dict[int, Dict[str, Any]]:
|
139
139
|
"""Create a mapping from domain IDs to their corresponding full description and significance score,
|
@@ -169,8 +169,9 @@ class Graph:
|
|
169
169
|
|
170
170
|
return domain_info_map
|
171
171
|
|
172
|
-
|
173
|
-
|
172
|
+
def _create_node_id_to_domain_ids_and_significances(
|
173
|
+
self, domains: pd.DataFrame
|
174
|
+
) -> Dict[int, Dict]:
|
174
175
|
"""Creates a dictionary mapping each node ID to its corresponding domain IDs and significance values.
|
175
176
|
|
176
177
|
Args:
|
@@ -216,54 +217,52 @@ class Graph:
|
|
216
217
|
|
217
218
|
return domain_id_to_label_map
|
218
219
|
|
220
|
+
def _unfold_sphere_to_plane(self, G: nx.Graph) -> nx.Graph:
|
221
|
+
"""Convert 3D coordinates to 2D by unfolding a sphere to a plane.
|
219
222
|
|
220
|
-
|
221
|
-
|
223
|
+
Args:
|
224
|
+
G (nx.Graph): A network graph with 3D coordinates. Each node should have 'x', 'y', and 'z' attributes.
|
222
225
|
|
223
|
-
|
224
|
-
|
226
|
+
Returns:
|
227
|
+
nx.Graph: The network graph with updated 2D coordinates (only 'x' and 'y').
|
228
|
+
"""
|
229
|
+
for node in G.nodes():
|
230
|
+
if "z" in G.nodes[node]:
|
231
|
+
# Extract 3D coordinates
|
232
|
+
x, y, z = G.nodes[node]["x"], G.nodes[node]["y"], G.nodes[node]["z"]
|
233
|
+
# Calculate spherical coordinates theta and phi from Cartesian coordinates
|
234
|
+
r = np.sqrt(x**2 + y**2 + z**2)
|
235
|
+
theta = np.arctan2(y, x)
|
236
|
+
phi = np.arccos(z / r)
|
237
|
+
|
238
|
+
# Convert spherical coordinates to 2D plane coordinates
|
239
|
+
unfolded_x = (theta + np.pi) / (2 * np.pi) # Shift and normalize theta to [0, 1]
|
240
|
+
unfolded_x = unfolded_x + 0.5 if unfolded_x < 0.5 else unfolded_x - 0.5
|
241
|
+
unfolded_y = (np.pi - phi) / np.pi # Reflect phi and normalize to [0, 1]
|
242
|
+
# Update network node attributes
|
243
|
+
G.nodes[node]["x"] = unfolded_x
|
244
|
+
G.nodes[node]["y"] = -unfolded_y
|
245
|
+
# Remove the 'z' coordinate as it's no longer needed
|
246
|
+
del G.nodes[node]["z"]
|
247
|
+
|
248
|
+
return G
|
249
|
+
|
250
|
+
def _extract_node_coordinates(self, G: nx.Graph) -> np.ndarray:
|
251
|
+
"""Extract 2D coordinates of nodes from the graph.
|
225
252
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
# Update network node attributes
|
243
|
-
G.nodes[node]["x"] = unfolded_x
|
244
|
-
G.nodes[node]["y"] = -unfolded_y
|
245
|
-
# Remove the 'z' coordinate as it's no longer needed
|
246
|
-
del G.nodes[node]["z"]
|
247
|
-
|
248
|
-
return G
|
249
|
-
|
250
|
-
|
251
|
-
def _extract_node_coordinates(G: nx.Graph) -> np.ndarray:
|
252
|
-
"""Extract 2D coordinates of nodes from the graph.
|
253
|
-
|
254
|
-
Args:
|
255
|
-
G (nx.Graph): The network graph with node coordinates.
|
256
|
-
|
257
|
-
Returns:
|
258
|
-
np.ndarray: Array of node coordinates with shape (num_nodes, 2).
|
259
|
-
"""
|
260
|
-
# Extract x and y coordinates from graph nodes
|
261
|
-
x_coords = dict(G.nodes.data("x"))
|
262
|
-
y_coords = dict(G.nodes.data("y"))
|
263
|
-
coordinates_dicts = [x_coords, y_coords]
|
264
|
-
# Combine x and y coordinates into a single array
|
265
|
-
node_positions = {
|
266
|
-
node: np.array([coords[node] for coords in coordinates_dicts]) for node in x_coords
|
267
|
-
}
|
268
|
-
node_coordinates = np.vstack(list(node_positions.values()))
|
269
|
-
return node_coordinates
|
253
|
+
Args:
|
254
|
+
G (nx.Graph): The network graph with node coordinates.
|
255
|
+
|
256
|
+
Returns:
|
257
|
+
np.ndarray: Array of node coordinates with shape (num_nodes, 2).
|
258
|
+
"""
|
259
|
+
# Extract x and y coordinates from graph nodes
|
260
|
+
x_coords = dict(G.nodes.data("x"))
|
261
|
+
y_coords = dict(G.nodes.data("y"))
|
262
|
+
coordinates_dicts = [x_coords, y_coords]
|
263
|
+
# Combine x and y coordinates into a single array
|
264
|
+
node_positions = {
|
265
|
+
node: np.array([coords[node] for coords in coordinates_dicts]) for node in x_coords
|
266
|
+
}
|
267
|
+
node_coordinates = np.vstack(list(node_positions.values()))
|
268
|
+
return node_coordinates
|
risk/network/graph/summary.py
CHANGED
@@ -9,7 +9,7 @@ import numpy as np
|
|
9
9
|
import pandas as pd
|
10
10
|
from statsmodels.stats.multitest import fdrcorrection
|
11
11
|
|
12
|
-
from risk.log.console import
|
12
|
+
from risk.log.console import log_header, logger
|
13
13
|
|
14
14
|
|
15
15
|
class Summary:
|
@@ -170,8 +170,7 @@ class Summary:
|
|
170
170
|
|
171
171
|
return results
|
172
172
|
|
173
|
-
|
174
|
-
def _calculate_qvalues(pvals: np.ndarray) -> np.ndarray:
|
173
|
+
def _calculate_qvalues(self, pvals: np.ndarray) -> np.ndarray:
|
175
174
|
"""Calculate q-values (FDR) for each row of a p-value matrix.
|
176
175
|
|
177
176
|
Args:
|
risk/network/io.py
CHANGED
@@ -15,8 +15,7 @@ import networkx as nx
|
|
15
15
|
import numpy as np
|
16
16
|
import pandas as pd
|
17
17
|
|
18
|
-
from risk.
|
19
|
-
from risk.log import params, logger, log_header
|
18
|
+
from risk.log import log_header, logger, params
|
20
19
|
|
21
20
|
|
22
21
|
class NetworkIO:
|
@@ -49,8 +48,8 @@ class NetworkIO:
|
|
49
48
|
min_edges_per_node=min_edges_per_node,
|
50
49
|
)
|
51
50
|
|
52
|
-
@staticmethod
|
53
51
|
def load_gpickle_network(
|
52
|
+
self,
|
54
53
|
filepath: str,
|
55
54
|
compute_sphere: bool = True,
|
56
55
|
surface_depth: float = 0.0,
|
@@ -94,8 +93,8 @@ class NetworkIO:
|
|
94
93
|
# Initialize the graph
|
95
94
|
return self._initialize_graph(G)
|
96
95
|
|
97
|
-
@staticmethod
|
98
96
|
def load_networkx_network(
|
97
|
+
self,
|
99
98
|
network: nx.Graph,
|
100
99
|
compute_sphere: bool = True,
|
101
100
|
surface_depth: float = 0.0,
|
@@ -138,8 +137,8 @@ class NetworkIO:
|
|
138
137
|
# Initialize the graph
|
139
138
|
return self._initialize_graph(network_copy)
|
140
139
|
|
141
|
-
@staticmethod
|
142
140
|
def load_cytoscape_network(
|
141
|
+
self,
|
143
142
|
filepath: str,
|
144
143
|
source_label: str = "source",
|
145
144
|
target_label: str = "target",
|
@@ -194,6 +193,7 @@ class NetworkIO:
|
|
194
193
|
|
195
194
|
Raises:
|
196
195
|
ValueError: If no matching attribute metadata file is found.
|
196
|
+
KeyError: If the source or target label is not found in the attribute table.
|
197
197
|
"""
|
198
198
|
filetype = "Cytoscape"
|
199
199
|
# Log the loading of the Cytoscape file
|
@@ -307,8 +307,8 @@ class NetworkIO:
|
|
307
307
|
if os.path.exists(tmp_dir):
|
308
308
|
shutil.rmtree(tmp_dir)
|
309
309
|
|
310
|
-
@staticmethod
|
311
310
|
def load_cytoscape_json_network(
|
311
|
+
self,
|
312
312
|
filepath: str,
|
313
313
|
source_label: str = "source",
|
314
314
|
target_label: str = "target",
|
@@ -437,7 +437,8 @@ class NetworkIO:
|
|
437
437
|
G.remove_nodes_from(nodes_to_remove)
|
438
438
|
|
439
439
|
# Remove isolated nodes
|
440
|
-
|
440
|
+
isolates = list(nx.isolates(G))
|
441
|
+
G.remove_nodes_from(isolates)
|
441
442
|
|
442
443
|
# Log the number of nodes and edges before and after cleaning
|
443
444
|
num_final_nodes = G.number_of_nodes()
|
@@ -523,11 +524,153 @@ class NetworkIO:
|
|
523
524
|
Args:
|
524
525
|
G (nx.Graph): The input network graph.
|
525
526
|
"""
|
526
|
-
|
527
|
+
G_transformed = self._prepare_graph_for_edge_length_assignment(
|
527
528
|
G,
|
528
529
|
compute_sphere=self.compute_sphere,
|
529
530
|
surface_depth=self.surface_depth,
|
530
531
|
)
|
532
|
+
self._calculate_and_set_edge_lengths(G_transformed, self.compute_sphere)
|
533
|
+
|
534
|
+
def _prepare_graph_for_edge_length_assignment(
|
535
|
+
self,
|
536
|
+
G: nx.Graph,
|
537
|
+
compute_sphere: bool = True,
|
538
|
+
surface_depth: float = 0.0,
|
539
|
+
) -> nx.Graph:
|
540
|
+
"""Prepare the graph by normalizing coordinates and optionally mapping nodes to a sphere.
|
541
|
+
|
542
|
+
Args:
|
543
|
+
G (nx.Graph): The input graph.
|
544
|
+
compute_sphere (bool): Whether to map nodes to a sphere. Defaults to True.
|
545
|
+
surface_depth (float): The surface depth for mapping to a sphere. Defaults to 0.0.
|
546
|
+
|
547
|
+
Returns:
|
548
|
+
nx.Graph: The graph with transformed coordinates.
|
549
|
+
"""
|
550
|
+
self._normalize_graph_coordinates(G)
|
551
|
+
|
552
|
+
if compute_sphere:
|
553
|
+
self._map_to_sphere(G)
|
554
|
+
G_depth = self._create_depth(G, surface_depth=surface_depth)
|
555
|
+
else:
|
556
|
+
G_depth = G
|
557
|
+
|
558
|
+
return G_depth
|
559
|
+
|
560
|
+
def _calculate_and_set_edge_lengths(self, G: nx.Graph, compute_sphere: bool) -> None:
|
561
|
+
"""Compute and assign edge lengths in the graph.
|
562
|
+
|
563
|
+
Args:
|
564
|
+
G (nx.Graph): The input graph.
|
565
|
+
compute_sphere (bool): Whether to compute spherical distances.
|
566
|
+
"""
|
567
|
+
|
568
|
+
def compute_distance_vectorized(coords, is_sphere):
|
569
|
+
"""Compute Euclidean or spherical distances between edges in bulk."""
|
570
|
+
u_coords, v_coords = coords[:, 0, :], coords[:, 1, :]
|
571
|
+
if is_sphere:
|
572
|
+
u_coords /= np.linalg.norm(u_coords, axis=1, keepdims=True)
|
573
|
+
v_coords /= np.linalg.norm(v_coords, axis=1, keepdims=True)
|
574
|
+
dot_products = np.einsum("ij,ij->i", u_coords, v_coords)
|
575
|
+
return np.arccos(np.clip(dot_products, -1.0, 1.0))
|
576
|
+
return np.linalg.norm(u_coords - v_coords, axis=1)
|
577
|
+
|
578
|
+
# Precompute edge coordinate arrays and compute distances in bulk
|
579
|
+
edge_data = np.array(
|
580
|
+
[
|
581
|
+
[
|
582
|
+
np.array([G.nodes[u]["x"], G.nodes[u]["y"], G.nodes[u].get("z", 0)]),
|
583
|
+
np.array([G.nodes[v]["x"], G.nodes[v]["y"], G.nodes[v].get("z", 0)]),
|
584
|
+
]
|
585
|
+
for u, v in G.edges
|
586
|
+
]
|
587
|
+
)
|
588
|
+
# Compute distances
|
589
|
+
distances = compute_distance_vectorized(edge_data, compute_sphere)
|
590
|
+
# Assign Euclidean or spherical distances to edges
|
591
|
+
for (u, v), distance in zip(G.edges, distances):
|
592
|
+
G.edges[u, v]["length"] = distance
|
593
|
+
|
594
|
+
def _map_to_sphere(self, G: nx.Graph) -> None:
|
595
|
+
"""Map the x and y coordinates of graph nodes onto a 3D sphere.
|
596
|
+
|
597
|
+
Args:
|
598
|
+
G (nx.Graph): The input graph with nodes having 'x' and 'y' coordinates.
|
599
|
+
"""
|
600
|
+
# Extract x, y coordinates as a NumPy array
|
601
|
+
nodes = list(G.nodes)
|
602
|
+
xy_coords = np.array([[G.nodes[node]["x"], G.nodes[node]["y"]] for node in nodes])
|
603
|
+
# Normalize coordinates between [0, 1]
|
604
|
+
min_vals = xy_coords.min(axis=0)
|
605
|
+
max_vals = xy_coords.max(axis=0)
|
606
|
+
normalized_xy = (xy_coords - min_vals) / (max_vals - min_vals)
|
607
|
+
# Convert normalized coordinates to spherical coordinates
|
608
|
+
theta = normalized_xy[:, 0] * np.pi * 2
|
609
|
+
phi = normalized_xy[:, 1] * np.pi
|
610
|
+
# Compute 3D Cartesian coordinates
|
611
|
+
x = np.sin(phi) * np.cos(theta)
|
612
|
+
y = np.sin(phi) * np.sin(theta)
|
613
|
+
z = np.cos(phi)
|
614
|
+
# Assign coordinates back to graph nodes in bulk
|
615
|
+
xyz_coords = {node: {"x": x[i], "y": y[i], "z": z[i]} for i, node in enumerate(nodes)}
|
616
|
+
nx.set_node_attributes(G, xyz_coords)
|
617
|
+
|
618
|
+
def _normalize_graph_coordinates(self, G: nx.Graph) -> None:
|
619
|
+
"""Normalize the x and y coordinates of the nodes in the graph to the [0, 1] range.
|
620
|
+
|
621
|
+
Args:
|
622
|
+
G (nx.Graph): The input graph with nodes having 'x' and 'y' coordinates.
|
623
|
+
"""
|
624
|
+
# Extract x, y coordinates from the graph nodes
|
625
|
+
xy_coords = np.array([[G.nodes[node]["x"], G.nodes[node]["y"]] for node in G.nodes()])
|
626
|
+
# Calculate min and max values for x and y
|
627
|
+
min_vals = np.min(xy_coords, axis=0)
|
628
|
+
max_vals = np.max(xy_coords, axis=0)
|
629
|
+
# Normalize the coordinates to [0, 1]
|
630
|
+
normalized_xy = (xy_coords - min_vals) / (max_vals - min_vals)
|
631
|
+
# Update the node coordinates with the normalized values
|
632
|
+
for i, node in enumerate(G.nodes()):
|
633
|
+
G.nodes[node]["x"], G.nodes[node]["y"] = normalized_xy[i]
|
634
|
+
|
635
|
+
def _create_depth(self, G: nx.Graph, surface_depth: float = 0.0) -> nx.Graph:
|
636
|
+
"""Adjust the 'z' attribute of each node based on the subcluster strengths and normalized surface depth.
|
637
|
+
|
638
|
+
Args:
|
639
|
+
G (nx.Graph): The input graph.
|
640
|
+
surface_depth (float): The maximum surface depth to apply for the strongest subcluster.
|
641
|
+
|
642
|
+
Returns:
|
643
|
+
nx.Graph: The graph with adjusted 'z' attribute for each node.
|
644
|
+
"""
|
645
|
+
if surface_depth >= 1.0:
|
646
|
+
surface_depth -= 1e-6 # Cap the surface depth to prevent a value of 1.0
|
647
|
+
|
648
|
+
# Compute subclusters as connected components
|
649
|
+
connected_components = list(nx.connected_components(G))
|
650
|
+
subcluster_strengths = {}
|
651
|
+
max_strength = 0
|
652
|
+
# Precompute strengths and track the maximum strength
|
653
|
+
for component in connected_components:
|
654
|
+
size = len(component)
|
655
|
+
max_strength = max(max_strength, size)
|
656
|
+
for node in component:
|
657
|
+
subcluster_strengths[node] = size
|
658
|
+
|
659
|
+
# Avoid repeated lookups and computations by pre-fetching node data
|
660
|
+
nodes = list(G.nodes(data=True))
|
661
|
+
node_updates = {}
|
662
|
+
for node, attrs in nodes:
|
663
|
+
strength = subcluster_strengths[node]
|
664
|
+
normalized_surface_depth = (strength / max_strength) * surface_depth
|
665
|
+
x, y, z = attrs["x"], attrs["y"], attrs["z"]
|
666
|
+
norm = np.sqrt(x**2 + y**2 + z**2)
|
667
|
+
adjusted_z = z - (z / norm) * normalized_surface_depth
|
668
|
+
node_updates[node] = {"z": adjusted_z}
|
669
|
+
|
670
|
+
# Batch update node attributes
|
671
|
+
nx.set_node_attributes(G, node_updates)
|
672
|
+
|
673
|
+
return G
|
531
674
|
|
532
675
|
def _log_loading(
|
533
676
|
self,
|
risk/network/plotter/__init__.py
CHANGED
risk/network/plotter/api.py
CHANGED
risk/network/plotter/canvas.py
CHANGED
@@ -158,7 +158,7 @@ class Canvas:
|
|
158
158
|
# Calculate the center and radius of the bounding box around the network
|
159
159
|
center, radius = calculate_bounding_box(node_coordinates)
|
160
160
|
# Adjust the center based on user-defined offsets
|
161
|
-
adjusted_center = _calculate_adjusted_center(
|
161
|
+
adjusted_center = self._calculate_adjusted_center(
|
162
162
|
center, radius, center_offset_x, center_offset_y
|
163
163
|
)
|
164
164
|
# Scale the radius by the scale factor
|
@@ -250,42 +250,42 @@ class Canvas:
|
|
250
250
|
fill_alpha=fill_alpha,
|
251
251
|
)
|
252
252
|
|
253
|
+
def _calculate_adjusted_center(
|
254
|
+
self,
|
255
|
+
center: Tuple[float, float],
|
256
|
+
radius: float,
|
257
|
+
center_offset_x: float = 0.0,
|
258
|
+
center_offset_y: float = 0.0,
|
259
|
+
) -> Tuple[float, float]:
|
260
|
+
"""Calculate the adjusted center for the network perimeter circle based on user-defined offsets.
|
253
261
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
center (Tuple[float, float]): Original center coordinates of the network graph.
|
264
|
-
radius (float): Radius of the bounding box around the network.
|
265
|
-
center_offset_x (float, optional): Horizontal offset as a fraction of the diameter.
|
266
|
-
Negative values shift the center left, positive values shift it right. Allowed
|
267
|
-
values are in the range [-1, 1]. Defaults to 0.0.
|
268
|
-
center_offset_y (float, optional): Vertical offset as a fraction of the diameter.
|
269
|
-
Negative values shift the center down, positive values shift it up. Allowed
|
270
|
-
values are in the range [-1, 1]. Defaults to 0.0.
|
262
|
+
Args:
|
263
|
+
center (Tuple[float, float]): Original center coordinates of the network graph.
|
264
|
+
radius (float): Radius of the bounding box around the network.
|
265
|
+
center_offset_x (float, optional): Horizontal offset as a fraction of the diameter.
|
266
|
+
Negative values shift the center left, positive values shift it right. Allowed
|
267
|
+
values are in the range [-1, 1]. Defaults to 0.0.
|
268
|
+
center_offset_y (float, optional): Vertical offset as a fraction of the diameter.
|
269
|
+
Negative values shift the center down, positive values shift it up. Allowed
|
270
|
+
values are in the range [-1, 1]. Defaults to 0.0.
|
271
271
|
|
272
|
-
|
273
|
-
|
272
|
+
Returns:
|
273
|
+
Tuple[float, float]: Adjusted center coordinates after applying the offsets.
|
274
274
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
275
|
+
Raises:
|
276
|
+
ValueError: If the center offsets are outside the valid range [-1, 1].
|
277
|
+
"""
|
278
|
+
# Flip the y-axis to match the plot orientation
|
279
|
+
flipped_center_offset_y = -center_offset_y
|
280
|
+
# Validate the center offsets
|
281
|
+
if not -1 <= center_offset_x <= 1:
|
282
|
+
raise ValueError("Horizontal center offset must be in the range [-1, 1].")
|
283
|
+
if not -1 <= center_offset_y <= 1:
|
284
|
+
raise ValueError("Vertical center offset must be in the range [-1, 1].")
|
285
285
|
|
286
|
-
|
287
|
-
|
288
|
-
|
286
|
+
# Calculate adjusted center by applying offset fractions of the diameter
|
287
|
+
adjusted_center_x = center[0] + (center_offset_x * radius * 2)
|
288
|
+
adjusted_center_y = center[1] + (flipped_center_offset_y * radius * 2)
|
289
289
|
|
290
|
-
|
291
|
-
|
290
|
+
# Return the adjusted center coordinates
|
291
|
+
return adjusted_center_x, adjusted_center_y
|
risk/network/plotter/contour.py
CHANGED
@@ -11,7 +11,7 @@ from scipy import linalg
|
|
11
11
|
from scipy.ndimage import label
|
12
12
|
from scipy.stats import gaussian_kde
|
13
13
|
|
14
|
-
from risk.log import
|
14
|
+
from risk.log import logger, params
|
15
15
|
from risk.network.graph.graph import Graph
|
16
16
|
from risk.network.plotter.utils.colors import get_annotated_domain_colors, to_rgba
|
17
17
|
|
@@ -213,7 +213,7 @@ class Contour:
|
|
213
213
|
]
|
214
214
|
z = kde(np.vstack([x.ravel(), y.ravel()])).reshape(x.shape)
|
215
215
|
# Check if the KDE forms a single connected component
|
216
|
-
connected = _is_connected(z)
|
216
|
+
connected = self._is_connected(z)
|
217
217
|
if not connected:
|
218
218
|
bandwidth += 0.05 # Increase bandwidth slightly and retry
|
219
219
|
except linalg.LinAlgError:
|
@@ -316,15 +316,14 @@ class Contour:
|
|
316
316
|
random_seed=random_seed,
|
317
317
|
)
|
318
318
|
|
319
|
+
def _is_connected(self, z: np.ndarray) -> bool:
|
320
|
+
"""Determine if a thresholded grid represents a single, connected component.
|
319
321
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
Args:
|
324
|
-
z (np.ndarray): A binary grid where the component connectivity is evaluated.
|
322
|
+
Args:
|
323
|
+
z (np.ndarray): A binary grid where the component connectivity is evaluated.
|
325
324
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
325
|
+
Returns:
|
326
|
+
bool: True if the grid represents a single connected component, False otherwise.
|
327
|
+
"""
|
328
|
+
_, num_features = label(z)
|
329
|
+
return num_features == 1 # Return True if only one connected component is found
|