nettracer3d 0.8.9__py3-none-any.whl → 0.9.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.
- nettracer3d/cellpose_manager.py +22 -11
- nettracer3d/modularity.py +24 -9
- nettracer3d/neighborhoods.py +46 -14
- nettracer3d/nettracer.py +286 -48
- nettracer3d/nettracer_gui.py +580 -201
- nettracer3d/proximity.py +101 -48
- {nettracer3d-0.8.9.dist-info → nettracer3d-0.9.0.dist-info}/METADATA +14 -4
- {nettracer3d-0.8.9.dist-info → nettracer3d-0.9.0.dist-info}/RECORD +12 -12
- {nettracer3d-0.8.9.dist-info → nettracer3d-0.9.0.dist-info}/WHEEL +0 -0
- {nettracer3d-0.8.9.dist-info → nettracer3d-0.9.0.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.8.9.dist-info → nettracer3d-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.8.9.dist-info → nettracer3d-0.9.0.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -102,7 +102,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
102
102
|
"WHITE": (1, 1, 1),
|
|
103
103
|
"GRAY": (0.5, 0.5, 0.5),
|
|
104
104
|
"LIGHT_GRAY": (0.8, 0.8, 0.8),
|
|
105
|
-
"DARK_GRAY": (0.2, 0.2, 0.2)
|
|
105
|
+
"DARK_GRAY": (0.2, 0.2, 0.2),
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
self.base_colors = [ #Channel colors
|
|
@@ -3471,12 +3471,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3471
3471
|
stats_menu = analysis_menu.addMenu("Stats")
|
|
3472
3472
|
allstats_action = stats_menu.addAction("Calculate Generic Network Stats")
|
|
3473
3473
|
allstats_action.triggered.connect(self.stats)
|
|
3474
|
-
histos_action = stats_menu.addAction("
|
|
3474
|
+
histos_action = stats_menu.addAction("Network Statistic Histograms")
|
|
3475
3475
|
histos_action.triggered.connect(self.histos)
|
|
3476
3476
|
radial_action = stats_menu.addAction("Radial Distribution Analysis")
|
|
3477
3477
|
radial_action.triggered.connect(self.show_radial_dialog)
|
|
3478
|
-
degree_dist_action = stats_menu.addAction("Degree Distribution Analysis")
|
|
3479
|
-
degree_dist_action.triggered.connect(self.show_degree_dist_dialog)
|
|
3480
3478
|
neighbor_id_action = stats_menu.addAction("Identity Distribution of Neighbors")
|
|
3481
3479
|
neighbor_id_action.triggered.connect(self.show_neighbor_id_dialog)
|
|
3482
3480
|
ripley_action = stats_menu.addAction("Ripley Clustering Analysis")
|
|
@@ -3515,7 +3513,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3515
3513
|
|
|
3516
3514
|
# Process menu
|
|
3517
3515
|
process_menu = menubar.addMenu("Process")
|
|
3518
|
-
calculate_menu = process_menu.addMenu("Calculate")
|
|
3516
|
+
calculate_menu = process_menu.addMenu("Calculate Network")
|
|
3519
3517
|
calc_all_action = calculate_menu.addAction("Calculate Connectivity Network (Find Node-Edge-Node Network)")
|
|
3520
3518
|
calc_all_action.triggered.connect(self.show_calc_all_dialog)
|
|
3521
3519
|
calc_prox_action = calculate_menu.addAction("Calculate Proximity Network (connect nodes by distance)")
|
|
@@ -3615,46 +3613,75 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3615
3613
|
menubar.setCornerWidget(cam_button, Qt.Corner.TopRightCorner)
|
|
3616
3614
|
|
|
3617
3615
|
def snap(self):
|
|
3618
|
-
|
|
3619
3616
|
try:
|
|
3620
|
-
|
|
3617
|
+
# Check if we have any data to save
|
|
3618
|
+
data = False
|
|
3621
3619
|
for thing in self.channel_data:
|
|
3622
3620
|
if thing is not None:
|
|
3623
3621
|
data = True
|
|
3622
|
+
break
|
|
3624
3623
|
if not data:
|
|
3625
3624
|
return
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3625
|
+
|
|
3626
|
+
# Get filename from user
|
|
3629
3627
|
filename, _ = QFileDialog.getSaveFileName(
|
|
3630
3628
|
self,
|
|
3631
3629
|
f"Save Image As",
|
|
3632
|
-
"",
|
|
3633
|
-
"TIFF Files (*.tif *.tiff);;All Files (*)"
|
|
3630
|
+
"",
|
|
3631
|
+
"PNG Files (*.png);;TIFF Files (*.tif *.tiff);;JPEG Files (*.jpg *.jpeg);;All Files (*)"
|
|
3634
3632
|
)
|
|
3635
3633
|
|
|
3636
|
-
if filename:
|
|
3637
|
-
#
|
|
3638
|
-
if
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3634
|
+
if filename:
|
|
3635
|
+
# Determine file extension
|
|
3636
|
+
if filename.lower().endswith(('.tif', '.tiff')):
|
|
3637
|
+
format_type = 'tiff'
|
|
3638
|
+
elif filename.lower().endswith(('.jpg', '.jpeg')):
|
|
3639
|
+
format_type = 'jpeg'
|
|
3640
|
+
elif filename.lower().endswith('.png'):
|
|
3641
|
+
format_type = 'png'
|
|
3642
|
+
else:
|
|
3643
|
+
filename += '.png'
|
|
3644
|
+
format_type = 'png'
|
|
3645
|
+
|
|
3646
|
+
# Method 1: Save with axes bbox (recommended)
|
|
3647
|
+
bbox = self.ax.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
|
|
3648
|
+
self.figure.savefig(filename,
|
|
3649
|
+
dpi=300,
|
|
3650
|
+
bbox_inches=bbox,
|
|
3651
|
+
facecolor='black',
|
|
3652
|
+
edgecolor='none',
|
|
3653
|
+
format=format_type,
|
|
3654
|
+
pad_inches=0)
|
|
3655
|
+
|
|
3656
|
+
print(f"Axes snapshot saved: {filename}")
|
|
3657
|
+
|
|
3658
|
+
except Exception as e:
|
|
3659
|
+
print(f"Error saving snapshot: {e}")
|
|
3646
3660
|
|
|
3647
3661
|
|
|
3648
3662
|
def open_cellpose(self):
|
|
3649
3663
|
|
|
3664
|
+
try:
|
|
3665
|
+
if self.shape[0] == 1:
|
|
3666
|
+
use_3d = False
|
|
3667
|
+
print("Launching 2D cellpose GUI")
|
|
3668
|
+
else:
|
|
3669
|
+
use_3d = True
|
|
3670
|
+
print("Launching 3D cellpose GUI")
|
|
3671
|
+
except:
|
|
3672
|
+
use_3d = True
|
|
3673
|
+
print("Launching 3D cellpose GUI")
|
|
3674
|
+
|
|
3650
3675
|
try:
|
|
3651
3676
|
|
|
3652
3677
|
from . import cellpose_manager
|
|
3653
3678
|
self.cellpose_launcher = cellpose_manager.CellposeGUILauncher(parent_widget=self)
|
|
3654
3679
|
|
|
3655
|
-
self.cellpose_launcher.launch_cellpose_gui()
|
|
3680
|
+
self.cellpose_launcher.launch_cellpose_gui(use_3d = use_3d)
|
|
3656
3681
|
|
|
3657
3682
|
except:
|
|
3683
|
+
import traceback
|
|
3684
|
+
print(traceback.format_exc())
|
|
3658
3685
|
pass
|
|
3659
3686
|
|
|
3660
3687
|
|
|
@@ -3680,106 +3707,26 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3680
3707
|
print(f"Error finding stats: {e}")
|
|
3681
3708
|
|
|
3682
3709
|
def histos(self):
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3710
|
+
"""
|
|
3711
|
+
Show a PyQt6 window with buttons to select which histogram to generate.
|
|
3712
|
+
Only calculates the histogram that the user selects.
|
|
3713
|
+
"""
|
|
3686
3714
|
try:
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
#
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
pl, cnts = np.unique(list(pls.values()), return_counts=True)
|
|
3701
|
-
path_lengths[pl] += cnts
|
|
3702
|
-
|
|
3703
|
-
# Express frequency distribution as a percentage (ignoring path lengths of 0)
|
|
3704
|
-
freq_percent = 100 * path_lengths[1:] / path_lengths[1:].sum()
|
|
3705
|
-
|
|
3706
|
-
# Plot the frequency distribution (ignoring path lengths of 0) as a percentage
|
|
3707
|
-
fig, ax = plt.subplots(figsize=(15, 8))
|
|
3708
|
-
ax.bar(np.arange(1, diameter + 1), height=freq_percent)
|
|
3709
|
-
ax.set_title(
|
|
3710
|
-
"Distribution of shortest path length in G", fontdict={"size": 35}, loc="center"
|
|
3711
|
-
)
|
|
3712
|
-
ax.set_xlabel("Shortest Path Length", fontdict={"size": 22})
|
|
3713
|
-
ax.set_ylabel("Frequency (%)", fontdict={"size": 22})
|
|
3714
|
-
|
|
3715
|
-
plt.show()
|
|
3716
|
-
freq_dict = {freq: length for length, freq in enumerate(freq_percent, start=1)}
|
|
3717
|
-
self.format_for_upperright_table(freq_dict, metric='Frequency (%)', value='Shortest Path Length', title="Distribution of shortest path length in G")
|
|
3718
|
-
|
|
3719
|
-
degree_centrality = nx.centrality.degree_centrality(G)
|
|
3720
|
-
plt.figure(figsize=(15, 8))
|
|
3721
|
-
plt.hist(degree_centrality.values(), bins=25)
|
|
3722
|
-
plt.xticks(ticks=[0, 0.025, 0.05, 0.1, 0.15, 0.2]) # set the x axis ticks
|
|
3723
|
-
plt.title("Degree Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
3724
|
-
plt.xlabel("Degree Centrality", fontdict={"size": 20})
|
|
3725
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3726
|
-
plt.show()
|
|
3727
|
-
self.format_for_upperright_table(degree_centrality, metric='Node', value='Degree Centrality', title="Degree Centrality Table")
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
betweenness_centrality = nx.centrality.betweenness_centrality(
|
|
3731
|
-
G
|
|
3732
|
-
)
|
|
3733
|
-
plt.figure(figsize=(15, 8))
|
|
3734
|
-
plt.hist(betweenness_centrality.values(), bins=100)
|
|
3735
|
-
plt.xticks(ticks=[0, 0.02, 0.1, 0.2, 0.3, 0.4, 0.5]) # set the x axis ticks
|
|
3736
|
-
plt.title("Betweenness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
3737
|
-
plt.xlabel("Betweenness Centrality", fontdict={"size": 20})
|
|
3738
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3739
|
-
plt.show()
|
|
3740
|
-
self.format_for_upperright_table(betweenness_centrality, metric='Node', value='Betweenness Centrality', title="Betweenness Centrality Table")
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
closeness_centrality = nx.centrality.closeness_centrality(
|
|
3744
|
-
G
|
|
3745
|
-
)
|
|
3746
|
-
plt.figure(figsize=(15, 8))
|
|
3747
|
-
plt.hist(closeness_centrality.values(), bins=60)
|
|
3748
|
-
plt.title("Closeness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
3749
|
-
plt.xlabel("Closeness Centrality", fontdict={"size": 20})
|
|
3750
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3751
|
-
plt.show()
|
|
3752
|
-
self.format_for_upperright_table(closeness_centrality, metric='Node', value='Closeness Centrality', title="Closeness Centrality Table")
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
eigenvector_centrality = nx.centrality.eigenvector_centrality(
|
|
3756
|
-
G
|
|
3757
|
-
)
|
|
3758
|
-
plt.figure(figsize=(15, 8))
|
|
3759
|
-
plt.hist(eigenvector_centrality.values(), bins=60)
|
|
3760
|
-
plt.xticks(ticks=[0, 0.01, 0.02, 0.04, 0.06, 0.08]) # set the x axis ticks
|
|
3761
|
-
plt.title("Eigenvector Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
3762
|
-
plt.xlabel("Eigenvector Centrality", fontdict={"size": 20})
|
|
3763
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3764
|
-
plt.show()
|
|
3765
|
-
self.format_for_upperright_table(eigenvector_centrality, metric='Node', value='Eigenvector Centrality', title="Eigenvector Centrality Table")
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
clusters = nx.clustering(G)
|
|
3770
|
-
plt.figure(figsize=(15, 8))
|
|
3771
|
-
plt.hist(clusters.values(), bins=50)
|
|
3772
|
-
plt.title("Clustering Coefficient Histogram ", fontdict={"size": 35}, loc="center")
|
|
3773
|
-
plt.xlabel("Clustering Coefficient", fontdict={"size": 20})
|
|
3774
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3775
|
-
plt.show()
|
|
3776
|
-
self.format_for_upperright_table(clusters, metric='Node', value='Clustering Coefficient', title="Clustering Coefficient Table")
|
|
3777
|
-
|
|
3778
|
-
bridges = list(nx.bridges(G))
|
|
3779
|
-
self.format_for_upperright_table(bridges, metric = 'Node Pair', title="Bridges")
|
|
3780
|
-
|
|
3715
|
+
# Create QApplication if it doesn't exist
|
|
3716
|
+
app = QApplication.instance()
|
|
3717
|
+
if app is None:
|
|
3718
|
+
app = QApplication(sys.argv)
|
|
3719
|
+
|
|
3720
|
+
# Create and show the histogram selector window
|
|
3721
|
+
self.histogram_selector = HistogramSelector(self)
|
|
3722
|
+
self.histogram_selector.show()
|
|
3723
|
+
|
|
3724
|
+
# Keep the window open (you might want to handle this differently based on your application structure)
|
|
3725
|
+
if not app.exec():
|
|
3726
|
+
pass # Window was closed
|
|
3727
|
+
|
|
3781
3728
|
except Exception as e:
|
|
3782
|
-
print(f"Error
|
|
3729
|
+
print(f"Error creating histogram selector: {e}")
|
|
3783
3730
|
|
|
3784
3731
|
def volumes(self):
|
|
3785
3732
|
|
|
@@ -5271,21 +5218,38 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5271
5218
|
else:
|
|
5272
5219
|
current_image = self.channel_data[channel]
|
|
5273
5220
|
|
|
5274
|
-
if is_rgb and self.channel_data[channel].shape[-1]
|
|
5275
|
-
# For RGB images,
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
self.
|
|
5280
|
-
|
|
5221
|
+
if is_rgb and self.channel_data[channel].shape[-1] in [3, 4]:
|
|
5222
|
+
# For RGB/RGBA images, use brightness/contrast to control alpha instead
|
|
5223
|
+
|
|
5224
|
+
# Calculate alpha based on brightness settings
|
|
5225
|
+
brightness_min = self.channel_brightness[channel]['min']
|
|
5226
|
+
brightness_max = self.channel_brightness[channel]['max']
|
|
5227
|
+
|
|
5228
|
+
# Map brightness range to alpha range (0.0 to 1.0)
|
|
5229
|
+
# brightness_min controls minimum alpha, brightness_max controls maximum alpha
|
|
5230
|
+
alpha_range = brightness_max - brightness_min
|
|
5231
|
+
base_alpha = brightness_min
|
|
5232
|
+
# You can adjust these multipliers to control the alpha range
|
|
5233
|
+
final_alpha = base_alpha + alpha_range # Scale to reasonable alpha range
|
|
5234
|
+
final_alpha = np.clip(final_alpha, 0.0, 1.0) # Ensure valid alpha range
|
|
5235
|
+
|
|
5236
|
+
# Display the image with brightness-controlled alpha
|
|
5237
|
+
if current_image.shape[-1] == 4:
|
|
5238
|
+
# For RGBA, multiply existing alpha by our brightness-controlled alpha
|
|
5239
|
+
img_with_alpha = current_image.copy()
|
|
5240
|
+
img_with_alpha[..., 3] = img_with_alpha[..., 3] * final_alpha
|
|
5241
|
+
self.ax.imshow(img_with_alpha)
|
|
5242
|
+
else:
|
|
5243
|
+
# For RGB, apply brightness-controlled alpha directly
|
|
5244
|
+
self.ax.imshow(current_image, alpha=final_alpha)
|
|
5245
|
+
|
|
5281
5246
|
else:
|
|
5282
|
-
# Regular channel processing with colormap
|
|
5247
|
+
# Regular channel processing with colormap (your existing code)
|
|
5283
5248
|
# Calculate brightness/contrast limits from entire volume
|
|
5284
|
-
if self.min_max[channel][0]
|
|
5285
|
-
self.min_max[channel][0] = np.min(channel)
|
|
5286
|
-
if self.min_max[channel][1]
|
|
5287
|
-
self.min_max[channel][1] = np.max(channel)
|
|
5288
|
-
|
|
5249
|
+
if self.min_max[channel][0] is None:
|
|
5250
|
+
self.min_max[channel][0] = np.min(self.channel_data[channel])
|
|
5251
|
+
if self.min_max[channel][1] is None:
|
|
5252
|
+
self.min_max[channel][1] = np.max(self.channel_data[channel])
|
|
5289
5253
|
img_min = self.min_max[channel][0]
|
|
5290
5254
|
img_max = self.min_max[channel][1]
|
|
5291
5255
|
|
|
@@ -5344,7 +5308,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5344
5308
|
)
|
|
5345
5309
|
self.ax.imshow(self.mini_overlay_data,
|
|
5346
5310
|
cmap=highlight_cmap,
|
|
5347
|
-
alpha=0.
|
|
5311
|
+
alpha=0.8)
|
|
5348
5312
|
elif self.highlight_overlay is not None and self.highlight and self.machine_window is None:
|
|
5349
5313
|
highlight_slice = self.highlight_overlay[self.current_slice]
|
|
5350
5314
|
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
@@ -5353,7 +5317,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5353
5317
|
)
|
|
5354
5318
|
self.ax.imshow(highlight_slice,
|
|
5355
5319
|
cmap=highlight_cmap,
|
|
5356
|
-
alpha=0.
|
|
5320
|
+
alpha=0.8)
|
|
5357
5321
|
elif self.highlight_overlay is not None and self.highlight:
|
|
5358
5322
|
highlight_slice = self.highlight_overlay[self.current_slice]
|
|
5359
5323
|
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
@@ -5366,7 +5330,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5366
5330
|
cmap=highlight_cmap,
|
|
5367
5331
|
vmin=0,
|
|
5368
5332
|
vmax=2, # Important: set vmax to 2 to accommodate both values
|
|
5369
|
-
alpha=0.
|
|
5333
|
+
alpha=0.3)
|
|
5370
5334
|
|
|
5371
5335
|
if self.channel_data[4] is not None:
|
|
5372
5336
|
|
|
@@ -5534,10 +5498,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5534
5498
|
dialog = RadialDialog(self)
|
|
5535
5499
|
dialog.exec()
|
|
5536
5500
|
|
|
5537
|
-
def show_degree_dist_dialog(self):
|
|
5538
|
-
dialog = DegreeDistDialog(self)
|
|
5539
|
-
dialog.exec()
|
|
5540
|
-
|
|
5541
5501
|
def show_neighbor_id_dialog(self):
|
|
5542
5502
|
dialog = NeighborIdentityDialog(self)
|
|
5543
5503
|
dialog.exec()
|
|
@@ -7122,6 +7082,15 @@ class ColorOverlayDialog(QDialog):
|
|
|
7122
7082
|
|
|
7123
7083
|
layout = QFormLayout(self)
|
|
7124
7084
|
|
|
7085
|
+
# Add mode selection dropdown
|
|
7086
|
+
self.mode_selector = QComboBox()
|
|
7087
|
+
self.mode_selector.addItems(["Nodes", "Edges"])
|
|
7088
|
+
if self.parent().active_channel == 0 and self.parent().channel_data[0] is not None:
|
|
7089
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
7090
|
+
else:
|
|
7091
|
+
self.mode_selector.setCurrentIndex(1) # Default to Mode 1
|
|
7092
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
7093
|
+
|
|
7125
7094
|
self.down_factor = QLineEdit("")
|
|
7126
7095
|
layout.addRow("down_factor (for speeding up overlay generation - optional):", self.down_factor)
|
|
7127
7096
|
|
|
@@ -7136,11 +7105,10 @@ class ColorOverlayDialog(QDialog):
|
|
|
7136
7105
|
|
|
7137
7106
|
down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
|
|
7138
7107
|
|
|
7139
|
-
|
|
7140
|
-
|
|
7108
|
+
mode = self.mode_selector.currentIndex()
|
|
7109
|
+
if mode == 0:
|
|
7141
7110
|
self.sort = 'Node'
|
|
7142
7111
|
else:
|
|
7143
|
-
mode = 1
|
|
7144
7112
|
self.sort = 'Edge'
|
|
7145
7113
|
|
|
7146
7114
|
|
|
@@ -7658,39 +7626,6 @@ class RadialDialog(QDialog):
|
|
|
7658
7626
|
except Exception as e:
|
|
7659
7627
|
print(f"An error occurred: {e}")
|
|
7660
7628
|
|
|
7661
|
-
class DegreeDistDialog(QDialog):
|
|
7662
|
-
|
|
7663
|
-
def __init__(self, parent=None):
|
|
7664
|
-
|
|
7665
|
-
super().__init__(parent)
|
|
7666
|
-
self.setWindowTitle("Degree Distribution Parameters")
|
|
7667
|
-
self.setModal(True)
|
|
7668
|
-
|
|
7669
|
-
layout = QFormLayout(self)
|
|
7670
|
-
|
|
7671
|
-
self.directory = QLineEdit("")
|
|
7672
|
-
layout.addRow("Output Directory:", self.directory)
|
|
7673
|
-
|
|
7674
|
-
# Add Run button
|
|
7675
|
-
run_button = QPushButton("Get Degree Distribution")
|
|
7676
|
-
run_button.clicked.connect(self.degreedist)
|
|
7677
|
-
layout.addWidget(run_button)
|
|
7678
|
-
|
|
7679
|
-
def degreedist(self):
|
|
7680
|
-
|
|
7681
|
-
try:
|
|
7682
|
-
|
|
7683
|
-
directory = str(self.distance.text()) if self.directory.text().strip() else None
|
|
7684
|
-
|
|
7685
|
-
degrees = my_network.degree_distribution(directory = directory)
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
self.parent().format_for_upperright_table(degrees, 'Degree (k)', 'Proportion of nodes with degree (p(k))', title = 'Degree Distribution Analysis')
|
|
7689
|
-
|
|
7690
|
-
self.accept()
|
|
7691
|
-
|
|
7692
|
-
except Exception as e:
|
|
7693
|
-
print(f"An error occurred: {e}")
|
|
7694
7629
|
|
|
7695
7630
|
class NearNeighDialog(QDialog):
|
|
7696
7631
|
def __init__(self, parent=None):
|
|
@@ -7725,9 +7660,12 @@ class NearNeighDialog(QDialog):
|
|
|
7725
7660
|
self.num = QLineEdit("1")
|
|
7726
7661
|
identities_layout.addRow("Number of Nearest Neighbors to Evaluate Per Node?:", self.num)
|
|
7727
7662
|
|
|
7728
|
-
|
|
7729
|
-
|
|
7663
|
+
self.centroids = QPushButton("Centroids")
|
|
7664
|
+
self.centroids.setCheckable(True)
|
|
7665
|
+
self.centroids.setChecked(True)
|
|
7666
|
+
identities_layout.addRow("Use Centroids? (Recommended for spheroids) Deselecting finds true nearest neighbors for mask but will be slower, and will only support a single nearest neighbor calculation for each root (rather than an avg)", self.centroids)
|
|
7730
7667
|
|
|
7668
|
+
main_layout.addWidget(identities_group)
|
|
7731
7669
|
|
|
7732
7670
|
# Optional Heatmap group box
|
|
7733
7671
|
heatmap_group = QGroupBox("Optional Heatmap")
|
|
@@ -7761,39 +7699,71 @@ class NearNeighDialog(QDialog):
|
|
|
7761
7699
|
|
|
7762
7700
|
main_layout.addWidget(quant_group)
|
|
7763
7701
|
|
|
7764
|
-
# Get Distribution group box
|
|
7702
|
+
# Get Distribution group box - ENHANCED STYLING
|
|
7765
7703
|
distribution_group = QGroupBox("Get Distribution")
|
|
7766
7704
|
distribution_layout = QVBoxLayout(distribution_group)
|
|
7767
7705
|
|
|
7768
|
-
run_button = QPushButton("Get Average Nearest Neighbor (Plus Distribution)")
|
|
7706
|
+
run_button = QPushButton("🔍 Get Average Nearest Neighbor (Plus Distribution)")
|
|
7707
|
+
# Style for primary action - blue with larger font
|
|
7708
|
+
run_button.setStyleSheet("""
|
|
7709
|
+
QPushButton {
|
|
7710
|
+
background-color: #2196F3;
|
|
7711
|
+
color: white;
|
|
7712
|
+
border: none;
|
|
7713
|
+
padding: 12px 20px;
|
|
7714
|
+
font-size: 14px;
|
|
7715
|
+
font-weight: bold;
|
|
7716
|
+
border-radius: 6px;
|
|
7717
|
+
}
|
|
7718
|
+
QPushButton:hover {
|
|
7719
|
+
background-color: #1976D2;
|
|
7720
|
+
}
|
|
7721
|
+
QPushButton:pressed {
|
|
7722
|
+
background-color: #0D47A1;
|
|
7723
|
+
}
|
|
7724
|
+
""")
|
|
7769
7725
|
run_button.clicked.connect(self.run)
|
|
7770
7726
|
distribution_layout.addWidget(run_button)
|
|
7771
7727
|
|
|
7772
7728
|
main_layout.addWidget(distribution_group)
|
|
7773
7729
|
|
|
7774
|
-
# Get All Averages group box (only if node_identities exists)
|
|
7730
|
+
# Get All Averages group box - ENHANCED STYLING (only if node_identities exists)
|
|
7775
7731
|
if my_network.node_identities is not None:
|
|
7776
7732
|
averages_group = QGroupBox("Get All Averages")
|
|
7777
7733
|
averages_layout = QVBoxLayout(averages_group)
|
|
7778
7734
|
|
|
7779
|
-
run_button2 = QPushButton("Get Average Nearest All ID Combinations (No Distribution, No Heatmap)")
|
|
7735
|
+
run_button2 = QPushButton("📊 Get Average Nearest All ID Combinations (No Distribution, No Heatmap)")
|
|
7736
|
+
# Style for secondary action - green with different styling
|
|
7737
|
+
run_button2.setStyleSheet("""
|
|
7738
|
+
QPushButton {
|
|
7739
|
+
background-color: #4CAF50;
|
|
7740
|
+
color: white;
|
|
7741
|
+
border: 2px solid #45a049;
|
|
7742
|
+
padding: 10px 16px;
|
|
7743
|
+
font-size: 13px;
|
|
7744
|
+
font-weight: normal;
|
|
7745
|
+
border-radius: 8px;
|
|
7746
|
+
}
|
|
7747
|
+
QPushButton:hover {
|
|
7748
|
+
background-color: #45a049;
|
|
7749
|
+
border-color: #3d8b40;
|
|
7750
|
+
}
|
|
7751
|
+
QPushButton:pressed {
|
|
7752
|
+
background-color: #3d8b40;
|
|
7753
|
+
}
|
|
7754
|
+
""")
|
|
7780
7755
|
run_button2.clicked.connect(self.run2)
|
|
7781
7756
|
averages_layout.addWidget(run_button2)
|
|
7782
7757
|
|
|
7783
7758
|
main_layout.addWidget(averages_group)
|
|
7784
7759
|
|
|
7785
7760
|
def toggle_map(self):
|
|
7786
|
-
|
|
7787
7761
|
if self.numpy.isChecked():
|
|
7788
|
-
|
|
7789
7762
|
if not self.map.isChecked():
|
|
7790
|
-
|
|
7791
7763
|
self.map.click()
|
|
7792
7764
|
|
|
7793
7765
|
def run(self):
|
|
7794
|
-
|
|
7795
7766
|
try:
|
|
7796
|
-
|
|
7797
7767
|
try:
|
|
7798
7768
|
root = self.root.currentText()
|
|
7799
7769
|
except:
|
|
@@ -7808,6 +7778,9 @@ class NearNeighDialog(QDialog):
|
|
|
7808
7778
|
numpy = self.numpy.isChecked()
|
|
7809
7779
|
num = int(self.num.text()) if self.num.text().strip() else 1
|
|
7810
7780
|
quant = self.quant.isChecked()
|
|
7781
|
+
centroids = self.centroids.isChecked()
|
|
7782
|
+
if not centroids:
|
|
7783
|
+
num = 1
|
|
7811
7784
|
|
|
7812
7785
|
if root is not None and targ is not None:
|
|
7813
7786
|
title = f"Nearest {num} Neighbor(s) Distance of {targ} from {root}"
|
|
@@ -7818,15 +7791,15 @@ class NearNeighDialog(QDialog):
|
|
|
7818
7791
|
header = f"Shortest Distance to Closest {num} Nodes"
|
|
7819
7792
|
header2 = "Root Node ID"
|
|
7820
7793
|
|
|
7821
|
-
if my_network.node_centroids is None:
|
|
7794
|
+
if centroids and my_network.node_centroids is None:
|
|
7822
7795
|
self.parent().show_centroid_dialog()
|
|
7823
7796
|
if my_network.node_centroids is None:
|
|
7824
7797
|
return
|
|
7825
7798
|
|
|
7826
7799
|
if not numpy:
|
|
7827
|
-
avg, output, quant_overlay = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, heatmap = heatmap, threed = threed, quant = quant)
|
|
7800
|
+
avg, output, quant_overlay = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, heatmap = heatmap, threed = threed, quant = quant, centroids = centroids)
|
|
7828
7801
|
else:
|
|
7829
|
-
avg, output, overlay, quant_overlay = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, heatmap = heatmap, threed = threed, numpy = True, quant = quant)
|
|
7802
|
+
avg, output, overlay, quant_overlay = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, heatmap = heatmap, threed = threed, numpy = True, quant = quant, centroids = centroids)
|
|
7830
7803
|
self.parent().load_channel(3, overlay, data = True)
|
|
7831
7804
|
|
|
7832
7805
|
if quant_overlay is not None:
|
|
@@ -7840,27 +7813,24 @@ class NearNeighDialog(QDialog):
|
|
|
7840
7813
|
except Exception as e:
|
|
7841
7814
|
import traceback
|
|
7842
7815
|
print(traceback.format_exc())
|
|
7843
|
-
|
|
7844
7816
|
print(f"Error: {e}")
|
|
7845
7817
|
|
|
7846
7818
|
def run2(self):
|
|
7847
|
-
|
|
7848
7819
|
try:
|
|
7849
|
-
|
|
7850
7820
|
available = list(set(my_network.node_identities.values()))
|
|
7851
|
-
|
|
7852
7821
|
num = int(self.num.text()) if self.num.text().strip() else 1
|
|
7853
7822
|
|
|
7823
|
+
centroids = self.centroids.isChecked()
|
|
7824
|
+
if not centroids:
|
|
7825
|
+
num = 1
|
|
7826
|
+
|
|
7854
7827
|
output_dict = {}
|
|
7855
7828
|
|
|
7856
7829
|
while len(available) > 1:
|
|
7857
|
-
|
|
7858
7830
|
root = available[0]
|
|
7859
7831
|
|
|
7860
7832
|
for targ in available:
|
|
7861
|
-
|
|
7862
|
-
avg, _, _ = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num)
|
|
7863
|
-
|
|
7833
|
+
avg, _, _ = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, centroids = centroids)
|
|
7864
7834
|
output_dict[f"{root} vs {targ}"] = avg
|
|
7865
7835
|
|
|
7866
7836
|
del available[0]
|
|
@@ -7870,7 +7840,6 @@ class NearNeighDialog(QDialog):
|
|
|
7870
7840
|
self.accept()
|
|
7871
7841
|
|
|
7872
7842
|
except Exception as e:
|
|
7873
|
-
|
|
7874
7843
|
print(f"Error: {e}")
|
|
7875
7844
|
|
|
7876
7845
|
|
|
@@ -10264,7 +10233,14 @@ class SegmentationWorker(QThread):
|
|
|
10264
10233
|
self.chunks_since_update = 0
|
|
10265
10234
|
self.last_update = current_time
|
|
10266
10235
|
|
|
10236
|
+
current_xlim = self.machine_window.parent().ax.get_xlim()
|
|
10237
|
+
|
|
10238
|
+
current_ylim = self.machine_window.parent().ax.get_ylim()
|
|
10239
|
+
|
|
10240
|
+
self.machine_window.parent().update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
10241
|
+
|
|
10267
10242
|
self.finished.emit()
|
|
10243
|
+
|
|
10268
10244
|
|
|
10269
10245
|
except Exception as e:
|
|
10270
10246
|
print(f"Error in segmentation: {e}")
|
|
@@ -11331,7 +11307,7 @@ class SkeletonizeDialog(QDialog):
|
|
|
11331
11307
|
)
|
|
11332
11308
|
|
|
11333
11309
|
if remove > 0:
|
|
11334
|
-
result = n3d.
|
|
11310
|
+
result = n3d.remove_branches_new(result, remove)
|
|
11335
11311
|
|
|
11336
11312
|
|
|
11337
11313
|
# Update both the display data and the network object
|
|
@@ -13128,6 +13104,409 @@ class ProxDialog(QDialog):
|
|
|
13128
13104
|
print(traceback.format_exc())
|
|
13129
13105
|
|
|
13130
13106
|
|
|
13107
|
+
class HistogramSelector(QWidget):
|
|
13108
|
+
def __init__(self, network_analysis_instance):
|
|
13109
|
+
super().__init__()
|
|
13110
|
+
self.network_analysis = network_analysis_instance
|
|
13111
|
+
self.G = my_network.network
|
|
13112
|
+
self.init_ui()
|
|
13113
|
+
|
|
13114
|
+
def init_ui(self):
|
|
13115
|
+
self.setWindowTitle('Network Analysis - Histogram Selector')
|
|
13116
|
+
self.setGeometry(300, 300, 400, 700) # Increased height for more buttons
|
|
13117
|
+
|
|
13118
|
+
layout = QVBoxLayout()
|
|
13119
|
+
|
|
13120
|
+
# Title label
|
|
13121
|
+
title_label = QLabel('Select Histogram to Generate:')
|
|
13122
|
+
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
13123
|
+
title_label.setStyleSheet("font-size: 16px; font-weight: bold; margin: 10px;")
|
|
13124
|
+
layout.addWidget(title_label)
|
|
13125
|
+
|
|
13126
|
+
# Create buttons for each histogram type
|
|
13127
|
+
self.create_button(layout, "Shortest Path Length Distribution", self.shortest_path_histogram)
|
|
13128
|
+
self.create_button(layout, "Degree Centrality", self.degree_centrality_histogram)
|
|
13129
|
+
self.create_button(layout, "Betweenness Centrality", self.betweenness_centrality_histogram)
|
|
13130
|
+
self.create_button(layout, "Closeness Centrality", self.closeness_centrality_histogram)
|
|
13131
|
+
self.create_button(layout, "Eigenvector Centrality", self.eigenvector_centrality_histogram)
|
|
13132
|
+
self.create_button(layout, "Clustering Coefficient", self.clustering_coefficient_histogram)
|
|
13133
|
+
self.create_button(layout, "Degree Distribution", self.degree_distribution_histogram)
|
|
13134
|
+
self.create_button(layout, "Node Connectivity", self.node_connectivity_histogram)
|
|
13135
|
+
self.create_button(layout, "Eccentricity", self.eccentricity_histogram)
|
|
13136
|
+
self.create_button(layout, "K-Core Decomposition", self.kcore_histogram)
|
|
13137
|
+
self.create_button(layout, "Triangle Count", self.triangle_count_histogram)
|
|
13138
|
+
self.create_button(layout, "Load Centrality", self.load_centrality_histogram)
|
|
13139
|
+
self.create_button(layout, "Communicability Betweenness Centrality", self.communicability_centrality_histogram)
|
|
13140
|
+
self.create_button(layout, "Harmonic Centrality", self.harmonic_centrality_histogram)
|
|
13141
|
+
self.create_button(layout, "Current Flow Betweenness", self.current_flow_betweenness_histogram)
|
|
13142
|
+
self.create_button(layout, "Dispersion", self.dispersion_histogram)
|
|
13143
|
+
self.create_button(layout, "Network Bridges", self.bridges_analysis)
|
|
13144
|
+
|
|
13145
|
+
# Close button
|
|
13146
|
+
close_button = QPushButton('Close')
|
|
13147
|
+
close_button.clicked.connect(self.close)
|
|
13148
|
+
close_button.setStyleSheet("QPushButton { background-color: #f44336; color: white; font-weight: bold; }")
|
|
13149
|
+
layout.addWidget(close_button)
|
|
13150
|
+
|
|
13151
|
+
self.setLayout(layout)
|
|
13152
|
+
|
|
13153
|
+
def create_button(self, layout, text, callback):
|
|
13154
|
+
button = QPushButton(text)
|
|
13155
|
+
button.clicked.connect(callback)
|
|
13156
|
+
button.setMinimumHeight(40)
|
|
13157
|
+
button.setStyleSheet("""
|
|
13158
|
+
QPushButton {
|
|
13159
|
+
background-color: #4CAF50;
|
|
13160
|
+
color: white;
|
|
13161
|
+
border: none;
|
|
13162
|
+
padding: 10px;
|
|
13163
|
+
font-size: 14px;
|
|
13164
|
+
font-weight: bold;
|
|
13165
|
+
border-radius: 5px;
|
|
13166
|
+
}
|
|
13167
|
+
QPushButton:hover {
|
|
13168
|
+
background-color: #45a049;
|
|
13169
|
+
}
|
|
13170
|
+
QPushButton:pressed {
|
|
13171
|
+
background-color: #3d8b40;
|
|
13172
|
+
}
|
|
13173
|
+
""")
|
|
13174
|
+
layout.addWidget(button)
|
|
13175
|
+
|
|
13176
|
+
def shortest_path_histogram(self):
|
|
13177
|
+
try:
|
|
13178
|
+
shortest_path_lengths = dict(nx.all_pairs_shortest_path_length(self.G))
|
|
13179
|
+
diameter = max(nx.eccentricity(self.G, sp=shortest_path_lengths).values())
|
|
13180
|
+
path_lengths = np.zeros(diameter + 1, dtype=int)
|
|
13181
|
+
|
|
13182
|
+
for pls in shortest_path_lengths.values():
|
|
13183
|
+
pl, cnts = np.unique(list(pls.values()), return_counts=True)
|
|
13184
|
+
path_lengths[pl] += cnts
|
|
13185
|
+
|
|
13186
|
+
freq_percent = 100 * path_lengths[1:] / path_lengths[1:].sum()
|
|
13187
|
+
|
|
13188
|
+
fig, ax = plt.subplots(figsize=(15, 8))
|
|
13189
|
+
ax.bar(np.arange(1, diameter + 1), height=freq_percent)
|
|
13190
|
+
ax.set_title(
|
|
13191
|
+
"Distribution of shortest path length in G", fontdict={"size": 35}, loc="center"
|
|
13192
|
+
)
|
|
13193
|
+
ax.set_xlabel("Shortest Path Length", fontdict={"size": 22})
|
|
13194
|
+
ax.set_ylabel("Frequency (%)", fontdict={"size": 22})
|
|
13195
|
+
plt.show()
|
|
13196
|
+
|
|
13197
|
+
freq_dict = {freq: length for length, freq in enumerate(freq_percent, start=1)}
|
|
13198
|
+
self.network_analysis.format_for_upperright_table(freq_dict, metric='Frequency (%)',
|
|
13199
|
+
value='Shortest Path Length',
|
|
13200
|
+
title="Distribution of shortest path length in G")
|
|
13201
|
+
except Exception as e:
|
|
13202
|
+
print(f"Error generating shortest path histogram: {e}")
|
|
13203
|
+
|
|
13204
|
+
def degree_centrality_histogram(self):
|
|
13205
|
+
try:
|
|
13206
|
+
degree_centrality = nx.centrality.degree_centrality(self.G)
|
|
13207
|
+
plt.figure(figsize=(15, 8))
|
|
13208
|
+
plt.hist(degree_centrality.values(), bins=25)
|
|
13209
|
+
plt.xticks(ticks=[0, 0.025, 0.05, 0.1, 0.15, 0.2])
|
|
13210
|
+
plt.title("Degree Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
13211
|
+
plt.xlabel("Degree Centrality", fontdict={"size": 20})
|
|
13212
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
13213
|
+
plt.show()
|
|
13214
|
+
self.network_analysis.format_for_upperright_table(degree_centrality, metric='Node',
|
|
13215
|
+
value='Degree Centrality',
|
|
13216
|
+
title="Degree Centrality Table")
|
|
13217
|
+
except Exception as e:
|
|
13218
|
+
print(f"Error generating degree centrality histogram: {e}")
|
|
13219
|
+
|
|
13220
|
+
def betweenness_centrality_histogram(self):
|
|
13221
|
+
try:
|
|
13222
|
+
betweenness_centrality = nx.centrality.betweenness_centrality(self.G)
|
|
13223
|
+
plt.figure(figsize=(15, 8))
|
|
13224
|
+
plt.hist(betweenness_centrality.values(), bins=100)
|
|
13225
|
+
plt.xticks(ticks=[0, 0.02, 0.1, 0.2, 0.3, 0.4, 0.5])
|
|
13226
|
+
plt.title("Betweenness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
13227
|
+
plt.xlabel("Betweenness Centrality", fontdict={"size": 20})
|
|
13228
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
13229
|
+
plt.show()
|
|
13230
|
+
self.network_analysis.format_for_upperright_table(betweenness_centrality, metric='Node',
|
|
13231
|
+
value='Betweenness Centrality',
|
|
13232
|
+
title="Betweenness Centrality Table")
|
|
13233
|
+
except Exception as e:
|
|
13234
|
+
print(f"Error generating betweenness centrality histogram: {e}")
|
|
13235
|
+
|
|
13236
|
+
def closeness_centrality_histogram(self):
|
|
13237
|
+
try:
|
|
13238
|
+
closeness_centrality = nx.centrality.closeness_centrality(self.G)
|
|
13239
|
+
plt.figure(figsize=(15, 8))
|
|
13240
|
+
plt.hist(closeness_centrality.values(), bins=60)
|
|
13241
|
+
plt.title("Closeness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
13242
|
+
plt.xlabel("Closeness Centrality", fontdict={"size": 20})
|
|
13243
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
13244
|
+
plt.show()
|
|
13245
|
+
self.network_analysis.format_for_upperright_table(closeness_centrality, metric='Node',
|
|
13246
|
+
value='Closeness Centrality',
|
|
13247
|
+
title="Closeness Centrality Table")
|
|
13248
|
+
except Exception as e:
|
|
13249
|
+
print(f"Error generating closeness centrality histogram: {e}")
|
|
13250
|
+
|
|
13251
|
+
def eigenvector_centrality_histogram(self):
|
|
13252
|
+
try:
|
|
13253
|
+
eigenvector_centrality = nx.centrality.eigenvector_centrality(self.G)
|
|
13254
|
+
plt.figure(figsize=(15, 8))
|
|
13255
|
+
plt.hist(eigenvector_centrality.values(), bins=60)
|
|
13256
|
+
plt.xticks(ticks=[0, 0.01, 0.02, 0.04, 0.06, 0.08])
|
|
13257
|
+
plt.title("Eigenvector Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
13258
|
+
plt.xlabel("Eigenvector Centrality", fontdict={"size": 20})
|
|
13259
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
13260
|
+
plt.show()
|
|
13261
|
+
self.network_analysis.format_for_upperright_table(eigenvector_centrality, metric='Node',
|
|
13262
|
+
value='Eigenvector Centrality',
|
|
13263
|
+
title="Eigenvector Centrality Table")
|
|
13264
|
+
except Exception as e:
|
|
13265
|
+
print(f"Error generating eigenvector centrality histogram: {e}")
|
|
13266
|
+
|
|
13267
|
+
def clustering_coefficient_histogram(self):
|
|
13268
|
+
try:
|
|
13269
|
+
clusters = nx.clustering(self.G)
|
|
13270
|
+
plt.figure(figsize=(15, 8))
|
|
13271
|
+
plt.hist(clusters.values(), bins=50)
|
|
13272
|
+
plt.title("Clustering Coefficient Histogram ", fontdict={"size": 35}, loc="center")
|
|
13273
|
+
plt.xlabel("Clustering Coefficient", fontdict={"size": 20})
|
|
13274
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
13275
|
+
plt.show()
|
|
13276
|
+
self.network_analysis.format_for_upperright_table(clusters, metric='Node',
|
|
13277
|
+
value='Clustering Coefficient',
|
|
13278
|
+
title="Clustering Coefficient Table")
|
|
13279
|
+
except Exception as e:
|
|
13280
|
+
print(f"Error generating clustering coefficient histogram: {e}")
|
|
13281
|
+
|
|
13282
|
+
def bridges_analysis(self):
|
|
13283
|
+
try:
|
|
13284
|
+
bridges = list(nx.bridges(self.G))
|
|
13285
|
+
self.network_analysis.format_for_upperright_table(bridges, metric='Node Pair',
|
|
13286
|
+
title="Bridges")
|
|
13287
|
+
except Exception as e:
|
|
13288
|
+
print(f"Error generating bridges analysis: {e}")
|
|
13289
|
+
|
|
13290
|
+
def degree_distribution_histogram(self):
|
|
13291
|
+
"""Raw degree distribution - very useful for understanding network topology"""
|
|
13292
|
+
try:
|
|
13293
|
+
degrees = [self.G.degree(n) for n in self.G.nodes()]
|
|
13294
|
+
plt.figure(figsize=(15, 8))
|
|
13295
|
+
plt.hist(degrees, bins=max(30, int(np.sqrt(len(degrees)))), alpha=0.7)
|
|
13296
|
+
plt.title("Degree Distribution", fontdict={"size": 35}, loc="center")
|
|
13297
|
+
plt.xlabel("Degree", fontdict={"size": 20})
|
|
13298
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13299
|
+
plt.yscale('log') # Often useful for degree distributions
|
|
13300
|
+
plt.show()
|
|
13301
|
+
|
|
13302
|
+
degree_dict = {node: deg for node, deg in self.G.degree()}
|
|
13303
|
+
self.network_analysis.format_for_upperright_table(degree_dict, metric='Node',
|
|
13304
|
+
value='Degree', title="Degree Distribution Table")
|
|
13305
|
+
except Exception as e:
|
|
13306
|
+
print(f"Error generating degree distribution histogram: {e}")
|
|
13307
|
+
|
|
13308
|
+
|
|
13309
|
+
def node_connectivity_histogram(self):
|
|
13310
|
+
"""Local node connectivity - minimum number of nodes that must be removed to disconnect neighbors"""
|
|
13311
|
+
try:
|
|
13312
|
+
if self.G.number_of_nodes() > 500: # Skip for large networks (computationally expensive)
|
|
13313
|
+
print("Note this analysis may be slow for large network (>500 nodes)")
|
|
13314
|
+
#return
|
|
13315
|
+
|
|
13316
|
+
connectivity = {}
|
|
13317
|
+
for node in self.G.nodes():
|
|
13318
|
+
neighbors = list(self.G.neighbors(node))
|
|
13319
|
+
if len(neighbors) > 1:
|
|
13320
|
+
connectivity[node] = nx.node_connectivity(self.G, neighbors[0], neighbors[1])
|
|
13321
|
+
else:
|
|
13322
|
+
connectivity[node] = 0
|
|
13323
|
+
|
|
13324
|
+
plt.figure(figsize=(15, 8))
|
|
13325
|
+
plt.hist(connectivity.values(), bins=20, alpha=0.7)
|
|
13326
|
+
plt.title("Node Connectivity Distribution", fontdict={"size": 35}, loc="center")
|
|
13327
|
+
plt.xlabel("Node Connectivity", fontdict={"size": 20})
|
|
13328
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13329
|
+
plt.show()
|
|
13330
|
+
self.network_analysis.format_for_upperright_table(connectivity, metric='Node',
|
|
13331
|
+
value='Connectivity', title="Node Connectivity Table")
|
|
13332
|
+
except Exception as e:
|
|
13333
|
+
print(f"Error generating node connectivity histogram: {e}")
|
|
13334
|
+
|
|
13335
|
+
def eccentricity_histogram(self):
|
|
13336
|
+
"""Eccentricity - maximum distance from a node to any other node"""
|
|
13337
|
+
try:
|
|
13338
|
+
if not nx.is_connected(self.G):
|
|
13339
|
+
print("Graph is not connected. Using largest connected component.")
|
|
13340
|
+
largest_cc = max(nx.connected_components(self.G), key=len)
|
|
13341
|
+
G_cc = self.G.subgraph(largest_cc)
|
|
13342
|
+
eccentricity = nx.eccentricity(G_cc)
|
|
13343
|
+
else:
|
|
13344
|
+
eccentricity = nx.eccentricity(self.G)
|
|
13345
|
+
|
|
13346
|
+
plt.figure(figsize=(15, 8))
|
|
13347
|
+
plt.hist(eccentricity.values(), bins=20, alpha=0.7)
|
|
13348
|
+
plt.title("Eccentricity Distribution", fontdict={"size": 35}, loc="center")
|
|
13349
|
+
plt.xlabel("Eccentricity", fontdict={"size": 20})
|
|
13350
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13351
|
+
plt.show()
|
|
13352
|
+
self.network_analysis.format_for_upperright_table(eccentricity, metric='Node',
|
|
13353
|
+
value='Eccentricity', title="Eccentricity Table")
|
|
13354
|
+
except Exception as e:
|
|
13355
|
+
print(f"Error generating eccentricity histogram: {e}")
|
|
13356
|
+
|
|
13357
|
+
def kcore_histogram(self):
|
|
13358
|
+
"""K-core decomposition - identifies cohesive subgroups"""
|
|
13359
|
+
try:
|
|
13360
|
+
kcore = nx.core_number(self.G)
|
|
13361
|
+
plt.figure(figsize=(15, 8))
|
|
13362
|
+
plt.hist(kcore.values(), bins=max(5, max(kcore.values())), alpha=0.7)
|
|
13363
|
+
plt.title("K-Core Distribution", fontdict={"size": 35}, loc="center")
|
|
13364
|
+
plt.xlabel("K-Core Number", fontdict={"size": 20})
|
|
13365
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13366
|
+
plt.show()
|
|
13367
|
+
self.network_analysis.format_for_upperright_table(kcore, metric='Node',
|
|
13368
|
+
value='K-Core', title="K-Core Table")
|
|
13369
|
+
except Exception as e:
|
|
13370
|
+
print(f"Error generating k-core histogram: {e}")
|
|
13371
|
+
|
|
13372
|
+
def triangle_count_histogram(self):
|
|
13373
|
+
"""Number of triangles each node participates in"""
|
|
13374
|
+
try:
|
|
13375
|
+
triangles = nx.triangles(self.G)
|
|
13376
|
+
plt.figure(figsize=(15, 8))
|
|
13377
|
+
plt.hist(triangles.values(), bins=30, alpha=0.7)
|
|
13378
|
+
plt.title("Triangle Count Distribution", fontdict={"size": 35}, loc="center")
|
|
13379
|
+
plt.xlabel("Number of Triangles", fontdict={"size": 20})
|
|
13380
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13381
|
+
plt.show()
|
|
13382
|
+
self.network_analysis.format_for_upperright_table(triangles, metric='Node',
|
|
13383
|
+
value='Triangle Count', title="Triangle Count Table")
|
|
13384
|
+
except Exception as e:
|
|
13385
|
+
print(f"Error generating triangle count histogram: {e}")
|
|
13386
|
+
|
|
13387
|
+
def load_centrality_histogram(self):
|
|
13388
|
+
"""Load centrality - fraction of shortest paths passing through each node"""
|
|
13389
|
+
try:
|
|
13390
|
+
if self.G.number_of_nodes() > 1000: # Skip for very large networks
|
|
13391
|
+
print("Note this analysis may be slow for large network (>1000 nodes)")
|
|
13392
|
+
#return
|
|
13393
|
+
|
|
13394
|
+
load_centrality = nx.load_centrality(self.G)
|
|
13395
|
+
plt.figure(figsize=(15, 8))
|
|
13396
|
+
plt.hist(load_centrality.values(), bins=50, alpha=0.7)
|
|
13397
|
+
plt.title("Load Centrality Distribution", fontdict={"size": 35}, loc="center")
|
|
13398
|
+
plt.xlabel("Load Centrality", fontdict={"size": 20})
|
|
13399
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13400
|
+
plt.show()
|
|
13401
|
+
self.network_analysis.format_for_upperright_table(load_centrality, metric='Node',
|
|
13402
|
+
value='Load Centrality', title="Load Centrality Table")
|
|
13403
|
+
except Exception as e:
|
|
13404
|
+
print(f"Error generating load centrality histogram: {e}")
|
|
13405
|
+
|
|
13406
|
+
def communicability_centrality_histogram(self):
|
|
13407
|
+
"""Communicability centrality - based on communicability between nodes"""
|
|
13408
|
+
try:
|
|
13409
|
+
if self.G.number_of_nodes() > 500: # Skip for large networks (memory intensive)
|
|
13410
|
+
print("Note this analysis may be slow for large network (>500 nodes)")
|
|
13411
|
+
#return
|
|
13412
|
+
|
|
13413
|
+
# Use the correct function name - it's in the communicability module
|
|
13414
|
+
comm_centrality = nx.communicability_betweenness_centrality(self.G)
|
|
13415
|
+
plt.figure(figsize=(15, 8))
|
|
13416
|
+
plt.hist(comm_centrality.values(), bins=50, alpha=0.7)
|
|
13417
|
+
plt.title("Communicability Betweenness Centrality Distribution", fontdict={"size": 35}, loc="center")
|
|
13418
|
+
plt.xlabel("Communicability Betweenness Centrality", fontdict={"size": 20})
|
|
13419
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13420
|
+
plt.show()
|
|
13421
|
+
self.network_analysis.format_for_upperright_table(comm_centrality, metric='Node',
|
|
13422
|
+
value='Communicability Betweenness Centrality',
|
|
13423
|
+
title="Communicability Betweenness Centrality Table")
|
|
13424
|
+
except Exception as e:
|
|
13425
|
+
print(f"Error generating communicability betweenness centrality histogram: {e}")
|
|
13426
|
+
|
|
13427
|
+
def harmonic_centrality_histogram(self):
|
|
13428
|
+
"""Harmonic centrality - better than closeness for disconnected networks"""
|
|
13429
|
+
try:
|
|
13430
|
+
harmonic_centrality = nx.harmonic_centrality(self.G)
|
|
13431
|
+
plt.figure(figsize=(15, 8))
|
|
13432
|
+
plt.hist(harmonic_centrality.values(), bins=50, alpha=0.7)
|
|
13433
|
+
plt.title("Harmonic Centrality Distribution", fontdict={"size": 35}, loc="center")
|
|
13434
|
+
plt.xlabel("Harmonic Centrality", fontdict={"size": 20})
|
|
13435
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13436
|
+
plt.show()
|
|
13437
|
+
self.network_analysis.format_for_upperright_table(harmonic_centrality, metric='Node',
|
|
13438
|
+
value='Harmonic Centrality',
|
|
13439
|
+
title="Harmonic Centrality Table")
|
|
13440
|
+
except Exception as e:
|
|
13441
|
+
print(f"Error generating harmonic centrality histogram: {e}")
|
|
13442
|
+
|
|
13443
|
+
def current_flow_betweenness_histogram(self):
|
|
13444
|
+
"""Current flow betweenness - models network as electrical circuit"""
|
|
13445
|
+
try:
|
|
13446
|
+
if self.G.number_of_nodes() > 500: # Skip for large networks (computationally expensive)
|
|
13447
|
+
print("Note this analysis may be slow for large network (>500 nodes)")
|
|
13448
|
+
#return
|
|
13449
|
+
|
|
13450
|
+
current_flow = nx.current_flow_betweenness_centrality(self.G)
|
|
13451
|
+
plt.figure(figsize=(15, 8))
|
|
13452
|
+
plt.hist(current_flow.values(), bins=50, alpha=0.7)
|
|
13453
|
+
plt.title("Current Flow Betweenness Centrality Distribution", fontdict={"size": 35}, loc="center")
|
|
13454
|
+
plt.xlabel("Current Flow Betweenness Centrality", fontdict={"size": 20})
|
|
13455
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13456
|
+
plt.show()
|
|
13457
|
+
self.network_analysis.format_for_upperright_table(current_flow, metric='Node',
|
|
13458
|
+
value='Current Flow Betweenness',
|
|
13459
|
+
title="Current Flow Betweenness Table")
|
|
13460
|
+
except Exception as e:
|
|
13461
|
+
print(f"Error generating current flow betweenness histogram: {e}")
|
|
13462
|
+
|
|
13463
|
+
def dispersion_histogram(self):
|
|
13464
|
+
"""Dispersion - measures how scattered a node's neighbors are"""
|
|
13465
|
+
try:
|
|
13466
|
+
if self.G.number_of_nodes() > 300: # Skip for large networks (very computationally expensive)
|
|
13467
|
+
print("Note this analysis may be slow for large network (>300 nodes)")
|
|
13468
|
+
#return
|
|
13469
|
+
|
|
13470
|
+
# Calculate average dispersion for each node
|
|
13471
|
+
dispersion_values = {}
|
|
13472
|
+
nodes = list(self.G.nodes())
|
|
13473
|
+
|
|
13474
|
+
for u in nodes:
|
|
13475
|
+
if self.G.degree(u) < 2: # Need at least 2 neighbors for dispersion
|
|
13476
|
+
dispersion_values[u] = 0
|
|
13477
|
+
continue
|
|
13478
|
+
|
|
13479
|
+
# Calculate dispersion for node u with all its neighbors
|
|
13480
|
+
neighbors = list(self.G.neighbors(u))
|
|
13481
|
+
if len(neighbors) < 2:
|
|
13482
|
+
dispersion_values[u] = 0
|
|
13483
|
+
continue
|
|
13484
|
+
|
|
13485
|
+
# Get dispersion scores for this node with all neighbors
|
|
13486
|
+
disp_scores = []
|
|
13487
|
+
for v in neighbors:
|
|
13488
|
+
try:
|
|
13489
|
+
disp_score = nx.dispersion(self.G, u, v)
|
|
13490
|
+
disp_scores.append(disp_score)
|
|
13491
|
+
except:
|
|
13492
|
+
continue
|
|
13493
|
+
|
|
13494
|
+
# Average dispersion for this node
|
|
13495
|
+
dispersion_values[u] = sum(disp_scores) / len(disp_scores) if disp_scores else 0
|
|
13496
|
+
|
|
13497
|
+
plt.figure(figsize=(15, 8))
|
|
13498
|
+
plt.hist(dispersion_values.values(), bins=30, alpha=0.7)
|
|
13499
|
+
plt.title("Average Dispersion Distribution", fontdict={"size": 35}, loc="center")
|
|
13500
|
+
plt.xlabel("Average Dispersion", fontdict={"size": 20})
|
|
13501
|
+
plt.ylabel("Frequency", fontdict={"size": 20})
|
|
13502
|
+
plt.show()
|
|
13503
|
+
self.network_analysis.format_for_upperright_table(dispersion_values, metric='Node',
|
|
13504
|
+
value='Average Dispersion',
|
|
13505
|
+
title="Average Dispersion Table")
|
|
13506
|
+
except Exception as e:
|
|
13507
|
+
print(f"Error generating dispersion histogram: {e}")
|
|
13508
|
+
|
|
13509
|
+
|
|
13131
13510
|
|
|
13132
13511
|
# Initiating this program from the script line:
|
|
13133
13512
|
|