nettracer3d 0.8.8__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.
Potentially problematic release.
This version of nettracer3d might be problematic. Click here for more details.
- 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 +735 -236
- nettracer3d/proximity.py +101 -48
- nettracer3d/segmenter.py +294 -186
- nettracer3d/segmenter_GPU.py +274 -158
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.9.0.dist-info}/METADATA +14 -4
- nettracer3d-0.9.0.dist-info/RECORD +25 -0
- nettracer3d-0.8.8.dist-info/RECORD +0 -25
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.9.0.dist-info}/WHEEL +0 -0
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.9.0.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.8.8.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
|
|
@@ -3161,6 +3161,18 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3161
3161
|
# End this stroke but keep session active for continuous painting
|
|
3162
3162
|
self.painting = False
|
|
3163
3163
|
|
|
3164
|
+
if self.erase:
|
|
3165
|
+
if (hasattr(self, 'virtual_draw_operations') and self.virtual_draw_operations) or \
|
|
3166
|
+
(hasattr(self, 'virtual_erase_operations') and self.virtual_erase_operations) or \
|
|
3167
|
+
(hasattr(self, 'current_operation') and self.current_operation):
|
|
3168
|
+
# Finish current operation first
|
|
3169
|
+
if hasattr(self, 'current_operation') and self.current_operation:
|
|
3170
|
+
self.pm.finish_current_virtual_operation()
|
|
3171
|
+
# Now convert to real data
|
|
3172
|
+
self.pm.convert_virtual_strokes_to_data()
|
|
3173
|
+
current_xlim = self.ax.get_xlim()
|
|
3174
|
+
current_ylim = self.ax.get_ylim()
|
|
3175
|
+
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
3164
3176
|
|
|
3165
3177
|
if self.resume:
|
|
3166
3178
|
self.machine_window.segmentation_worker.resume()
|
|
@@ -3459,12 +3471,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3459
3471
|
stats_menu = analysis_menu.addMenu("Stats")
|
|
3460
3472
|
allstats_action = stats_menu.addAction("Calculate Generic Network Stats")
|
|
3461
3473
|
allstats_action.triggered.connect(self.stats)
|
|
3462
|
-
histos_action = stats_menu.addAction("
|
|
3474
|
+
histos_action = stats_menu.addAction("Network Statistic Histograms")
|
|
3463
3475
|
histos_action.triggered.connect(self.histos)
|
|
3464
3476
|
radial_action = stats_menu.addAction("Radial Distribution Analysis")
|
|
3465
3477
|
radial_action.triggered.connect(self.show_radial_dialog)
|
|
3466
|
-
degree_dist_action = stats_menu.addAction("Degree Distribution Analysis")
|
|
3467
|
-
degree_dist_action.triggered.connect(self.show_degree_dist_dialog)
|
|
3468
3478
|
neighbor_id_action = stats_menu.addAction("Identity Distribution of Neighbors")
|
|
3469
3479
|
neighbor_id_action.triggered.connect(self.show_neighbor_id_dialog)
|
|
3470
3480
|
ripley_action = stats_menu.addAction("Ripley Clustering Analysis")
|
|
@@ -3503,7 +3513,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3503
3513
|
|
|
3504
3514
|
# Process menu
|
|
3505
3515
|
process_menu = menubar.addMenu("Process")
|
|
3506
|
-
calculate_menu = process_menu.addMenu("Calculate")
|
|
3516
|
+
calculate_menu = process_menu.addMenu("Calculate Network")
|
|
3507
3517
|
calc_all_action = calculate_menu.addAction("Calculate Connectivity Network (Find Node-Edge-Node Network)")
|
|
3508
3518
|
calc_all_action.triggered.connect(self.show_calc_all_dialog)
|
|
3509
3519
|
calc_prox_action = calculate_menu.addAction("Calculate Proximity Network (connect nodes by distance)")
|
|
@@ -3603,46 +3613,75 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3603
3613
|
menubar.setCornerWidget(cam_button, Qt.Corner.TopRightCorner)
|
|
3604
3614
|
|
|
3605
3615
|
def snap(self):
|
|
3606
|
-
|
|
3607
3616
|
try:
|
|
3608
|
-
|
|
3617
|
+
# Check if we have any data to save
|
|
3618
|
+
data = False
|
|
3609
3619
|
for thing in self.channel_data:
|
|
3610
3620
|
if thing is not None:
|
|
3611
3621
|
data = True
|
|
3622
|
+
break
|
|
3612
3623
|
if not data:
|
|
3613
3624
|
return
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3625
|
+
|
|
3626
|
+
# Get filename from user
|
|
3617
3627
|
filename, _ = QFileDialog.getSaveFileName(
|
|
3618
3628
|
self,
|
|
3619
3629
|
f"Save Image As",
|
|
3620
|
-
"",
|
|
3621
|
-
"TIFF Files (*.tif *.tiff);;All Files (*)"
|
|
3630
|
+
"",
|
|
3631
|
+
"PNG Files (*.png);;TIFF Files (*.tif *.tiff);;JPEG Files (*.jpg *.jpeg);;All Files (*)"
|
|
3622
3632
|
)
|
|
3623
3633
|
|
|
3624
|
-
if filename:
|
|
3625
|
-
#
|
|
3626
|
-
if
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
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}")
|
|
3634
3660
|
|
|
3635
3661
|
|
|
3636
3662
|
def open_cellpose(self):
|
|
3637
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
|
+
|
|
3638
3675
|
try:
|
|
3639
3676
|
|
|
3640
3677
|
from . import cellpose_manager
|
|
3641
3678
|
self.cellpose_launcher = cellpose_manager.CellposeGUILauncher(parent_widget=self)
|
|
3642
3679
|
|
|
3643
|
-
self.cellpose_launcher.launch_cellpose_gui()
|
|
3680
|
+
self.cellpose_launcher.launch_cellpose_gui(use_3d = use_3d)
|
|
3644
3681
|
|
|
3645
3682
|
except:
|
|
3683
|
+
import traceback
|
|
3684
|
+
print(traceback.format_exc())
|
|
3646
3685
|
pass
|
|
3647
3686
|
|
|
3648
3687
|
|
|
@@ -3668,106 +3707,26 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3668
3707
|
print(f"Error finding stats: {e}")
|
|
3669
3708
|
|
|
3670
3709
|
def histos(self):
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3710
|
+
"""
|
|
3711
|
+
Show a PyQt6 window with buttons to select which histogram to generate.
|
|
3712
|
+
Only calculates the histogram that the user selects.
|
|
3713
|
+
"""
|
|
3674
3714
|
try:
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
#
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
pl, cnts = np.unique(list(pls.values()), return_counts=True)
|
|
3689
|
-
path_lengths[pl] += cnts
|
|
3690
|
-
|
|
3691
|
-
# Express frequency distribution as a percentage (ignoring path lengths of 0)
|
|
3692
|
-
freq_percent = 100 * path_lengths[1:] / path_lengths[1:].sum()
|
|
3693
|
-
|
|
3694
|
-
# Plot the frequency distribution (ignoring path lengths of 0) as a percentage
|
|
3695
|
-
fig, ax = plt.subplots(figsize=(15, 8))
|
|
3696
|
-
ax.bar(np.arange(1, diameter + 1), height=freq_percent)
|
|
3697
|
-
ax.set_title(
|
|
3698
|
-
"Distribution of shortest path length in G", fontdict={"size": 35}, loc="center"
|
|
3699
|
-
)
|
|
3700
|
-
ax.set_xlabel("Shortest Path Length", fontdict={"size": 22})
|
|
3701
|
-
ax.set_ylabel("Frequency (%)", fontdict={"size": 22})
|
|
3702
|
-
|
|
3703
|
-
plt.show()
|
|
3704
|
-
freq_dict = {freq: length for length, freq in enumerate(freq_percent, start=1)}
|
|
3705
|
-
self.format_for_upperright_table(freq_dict, metric='Frequency (%)', value='Shortest Path Length', title="Distribution of shortest path length in G")
|
|
3706
|
-
|
|
3707
|
-
degree_centrality = nx.centrality.degree_centrality(G)
|
|
3708
|
-
plt.figure(figsize=(15, 8))
|
|
3709
|
-
plt.hist(degree_centrality.values(), bins=25)
|
|
3710
|
-
plt.xticks(ticks=[0, 0.025, 0.05, 0.1, 0.15, 0.2]) # set the x axis ticks
|
|
3711
|
-
plt.title("Degree Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
3712
|
-
plt.xlabel("Degree Centrality", fontdict={"size": 20})
|
|
3713
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3714
|
-
plt.show()
|
|
3715
|
-
self.format_for_upperright_table(degree_centrality, metric='Node', value='Degree Centrality', title="Degree Centrality Table")
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
betweenness_centrality = nx.centrality.betweenness_centrality(
|
|
3719
|
-
G
|
|
3720
|
-
)
|
|
3721
|
-
plt.figure(figsize=(15, 8))
|
|
3722
|
-
plt.hist(betweenness_centrality.values(), bins=100)
|
|
3723
|
-
plt.xticks(ticks=[0, 0.02, 0.1, 0.2, 0.3, 0.4, 0.5]) # set the x axis ticks
|
|
3724
|
-
plt.title("Betweenness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
3725
|
-
plt.xlabel("Betweenness Centrality", fontdict={"size": 20})
|
|
3726
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3727
|
-
plt.show()
|
|
3728
|
-
self.format_for_upperright_table(betweenness_centrality, metric='Node', value='Betweenness Centrality', title="Betweenness Centrality Table")
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
closeness_centrality = nx.centrality.closeness_centrality(
|
|
3732
|
-
G
|
|
3733
|
-
)
|
|
3734
|
-
plt.figure(figsize=(15, 8))
|
|
3735
|
-
plt.hist(closeness_centrality.values(), bins=60)
|
|
3736
|
-
plt.title("Closeness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
3737
|
-
plt.xlabel("Closeness Centrality", fontdict={"size": 20})
|
|
3738
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3739
|
-
plt.show()
|
|
3740
|
-
self.format_for_upperright_table(closeness_centrality, metric='Node', value='Closeness Centrality', title="Closeness Centrality Table")
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
eigenvector_centrality = nx.centrality.eigenvector_centrality(
|
|
3744
|
-
G
|
|
3745
|
-
)
|
|
3746
|
-
plt.figure(figsize=(15, 8))
|
|
3747
|
-
plt.hist(eigenvector_centrality.values(), bins=60)
|
|
3748
|
-
plt.xticks(ticks=[0, 0.01, 0.02, 0.04, 0.06, 0.08]) # set the x axis ticks
|
|
3749
|
-
plt.title("Eigenvector Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
3750
|
-
plt.xlabel("Eigenvector Centrality", fontdict={"size": 20})
|
|
3751
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3752
|
-
plt.show()
|
|
3753
|
-
self.format_for_upperright_table(eigenvector_centrality, metric='Node', value='Eigenvector Centrality', title="Eigenvector Centrality Table")
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
clusters = nx.clustering(G)
|
|
3758
|
-
plt.figure(figsize=(15, 8))
|
|
3759
|
-
plt.hist(clusters.values(), bins=50)
|
|
3760
|
-
plt.title("Clustering Coefficient Histogram ", fontdict={"size": 35}, loc="center")
|
|
3761
|
-
plt.xlabel("Clustering Coefficient", fontdict={"size": 20})
|
|
3762
|
-
plt.ylabel("Counts", fontdict={"size": 20})
|
|
3763
|
-
plt.show()
|
|
3764
|
-
self.format_for_upperright_table(clusters, metric='Node', value='Clustering Coefficient', title="Clustering Coefficient Table")
|
|
3765
|
-
|
|
3766
|
-
bridges = list(nx.bridges(G))
|
|
3767
|
-
self.format_for_upperright_table(bridges, metric = 'Node Pair', title="Bridges")
|
|
3768
|
-
|
|
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
|
+
|
|
3769
3728
|
except Exception as e:
|
|
3770
|
-
print(f"Error
|
|
3729
|
+
print(f"Error creating histogram selector: {e}")
|
|
3771
3730
|
|
|
3772
3731
|
def volumes(self):
|
|
3773
3732
|
|
|
@@ -4779,7 +4738,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4779
4738
|
try:
|
|
4780
4739
|
#if len(self.channel_data[channel_index].shape) == 4:
|
|
4781
4740
|
if 1 in self.channel_data[channel_index].shape:
|
|
4782
|
-
print("Removing singleton dimension (I am assuming this is a channel dimension?)")
|
|
4741
|
+
#print("Removing singleton dimension (I am assuming this is a channel dimension?)")
|
|
4783
4742
|
self.channel_data[channel_index] = np.squeeze(self.channel_data[channel_index])
|
|
4784
4743
|
except:
|
|
4785
4744
|
pass
|
|
@@ -4907,6 +4866,14 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4907
4866
|
self.original_ylim, self.original_xlim = (self.shape[1] + 0.5, -0.5), (-0.5, self.shape[2] - 0.5)
|
|
4908
4867
|
#print(self.original_xlim)
|
|
4909
4868
|
|
|
4869
|
+
self.completed_paint_strokes = [] #Reset pending paint operations
|
|
4870
|
+
self.current_stroke_points = []
|
|
4871
|
+
self.current_stroke_type = None
|
|
4872
|
+
self.virtual_draw_operations = []
|
|
4873
|
+
self.virtual_erase_operations = []
|
|
4874
|
+
self.current_operation = []
|
|
4875
|
+
self.current_operation_type = None
|
|
4876
|
+
|
|
4910
4877
|
if not end_paint:
|
|
4911
4878
|
|
|
4912
4879
|
self.update_display(reset_resize = reset_resize, preserve_zoom = preserve_zoom)
|
|
@@ -5143,8 +5110,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5143
5110
|
if self.mini_overlay == True: #If we are rendering the highlight overlay for selected values one at a time.
|
|
5144
5111
|
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
5145
5112
|
self.update_display(preserve_zoom=view_settings)
|
|
5146
|
-
if self.machine_window is not None:
|
|
5147
|
-
self.machine_window.poke_segmenter()
|
|
5113
|
+
#if self.machine_window is not None:
|
|
5114
|
+
#self.machine_window.poke_segmenter()
|
|
5148
5115
|
self.pending_slice = None
|
|
5149
5116
|
|
|
5150
5117
|
def update_brightness(self, channel_index, values):
|
|
@@ -5251,21 +5218,38 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5251
5218
|
else:
|
|
5252
5219
|
current_image = self.channel_data[channel]
|
|
5253
5220
|
|
|
5254
|
-
if is_rgb and self.channel_data[channel].shape[-1]
|
|
5255
|
-
# For RGB images,
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
self.
|
|
5260
|
-
|
|
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
|
+
|
|
5261
5246
|
else:
|
|
5262
|
-
# Regular channel processing with colormap
|
|
5247
|
+
# Regular channel processing with colormap (your existing code)
|
|
5263
5248
|
# Calculate brightness/contrast limits from entire volume
|
|
5264
|
-
if self.min_max[channel][0]
|
|
5265
|
-
self.min_max[channel][0] = np.min(channel)
|
|
5266
|
-
if self.min_max[channel][1]
|
|
5267
|
-
self.min_max[channel][1] = np.max(channel)
|
|
5268
|
-
|
|
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])
|
|
5269
5253
|
img_min = self.min_max[channel][0]
|
|
5270
5254
|
img_max = self.min_max[channel][1]
|
|
5271
5255
|
|
|
@@ -5324,7 +5308,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5324
5308
|
)
|
|
5325
5309
|
self.ax.imshow(self.mini_overlay_data,
|
|
5326
5310
|
cmap=highlight_cmap,
|
|
5327
|
-
alpha=0.
|
|
5311
|
+
alpha=0.8)
|
|
5328
5312
|
elif self.highlight_overlay is not None and self.highlight and self.machine_window is None:
|
|
5329
5313
|
highlight_slice = self.highlight_overlay[self.current_slice]
|
|
5330
5314
|
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
@@ -5333,7 +5317,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5333
5317
|
)
|
|
5334
5318
|
self.ax.imshow(highlight_slice,
|
|
5335
5319
|
cmap=highlight_cmap,
|
|
5336
|
-
alpha=0.
|
|
5320
|
+
alpha=0.8)
|
|
5337
5321
|
elif self.highlight_overlay is not None and self.highlight:
|
|
5338
5322
|
highlight_slice = self.highlight_overlay[self.current_slice]
|
|
5339
5323
|
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
@@ -5346,7 +5330,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5346
5330
|
cmap=highlight_cmap,
|
|
5347
5331
|
vmin=0,
|
|
5348
5332
|
vmax=2, # Important: set vmax to 2 to accommodate both values
|
|
5349
|
-
alpha=0.
|
|
5333
|
+
alpha=0.3)
|
|
5350
5334
|
|
|
5351
5335
|
if self.channel_data[4] is not None:
|
|
5352
5336
|
|
|
@@ -5514,10 +5498,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5514
5498
|
dialog = RadialDialog(self)
|
|
5515
5499
|
dialog.exec()
|
|
5516
5500
|
|
|
5517
|
-
def show_degree_dist_dialog(self):
|
|
5518
|
-
dialog = DegreeDistDialog(self)
|
|
5519
|
-
dialog.exec()
|
|
5520
|
-
|
|
5521
5501
|
def show_neighbor_id_dialog(self):
|
|
5522
5502
|
dialog = NeighborIdentityDialog(self)
|
|
5523
5503
|
dialog.exec()
|
|
@@ -6478,6 +6458,13 @@ class BrightnessContrastDialog(QDialog):
|
|
|
6478
6458
|
channel_layout.addWidget(slider_container)
|
|
6479
6459
|
layout.addWidget(channel_widget)
|
|
6480
6460
|
|
|
6461
|
+
#debouncing
|
|
6462
|
+
self.debounce_timer = QTimer()
|
|
6463
|
+
self.debounce_timer.setSingleShot(True)
|
|
6464
|
+
self.debounce_timer.timeout.connect(self._apply_pending_updates)
|
|
6465
|
+
self.pending_updates = {}
|
|
6466
|
+
self.debounce_delay = 300 # 300ms delay
|
|
6467
|
+
|
|
6481
6468
|
# Connect signals
|
|
6482
6469
|
slider.valueChanged.connect(lambda values, ch=i: self.on_slider_change(ch, values))
|
|
6483
6470
|
min_input.editingFinished.connect(lambda ch=i: self.on_min_input_change(ch))
|
|
@@ -6488,7 +6475,18 @@ class BrightnessContrastDialog(QDialog):
|
|
|
6488
6475
|
min_val, max_val = values
|
|
6489
6476
|
self.min_inputs[channel].setText(str(min_val))
|
|
6490
6477
|
self.max_inputs[channel].setText(str(max_val))
|
|
6491
|
-
|
|
6478
|
+
|
|
6479
|
+
# Store the pending update
|
|
6480
|
+
self.pending_updates[channel] = values
|
|
6481
|
+
|
|
6482
|
+
# Restart the debounce timer
|
|
6483
|
+
self.debounce_timer.start(self.debounce_delay)
|
|
6484
|
+
|
|
6485
|
+
def _apply_pending_updates(self):
|
|
6486
|
+
"""Apply all pending brightness updates"""
|
|
6487
|
+
for channel, values in self.pending_updates.items():
|
|
6488
|
+
self.parent().update_brightness(channel, values)
|
|
6489
|
+
self.pending_updates.clear()
|
|
6492
6490
|
|
|
6493
6491
|
def on_min_input_change(self, channel):
|
|
6494
6492
|
"""Handle changes to minimum value input"""
|
|
@@ -7084,6 +7082,15 @@ class ColorOverlayDialog(QDialog):
|
|
|
7084
7082
|
|
|
7085
7083
|
layout = QFormLayout(self)
|
|
7086
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
|
+
|
|
7087
7094
|
self.down_factor = QLineEdit("")
|
|
7088
7095
|
layout.addRow("down_factor (for speeding up overlay generation - optional):", self.down_factor)
|
|
7089
7096
|
|
|
@@ -7098,11 +7105,10 @@ class ColorOverlayDialog(QDialog):
|
|
|
7098
7105
|
|
|
7099
7106
|
down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
|
|
7100
7107
|
|
|
7101
|
-
|
|
7102
|
-
|
|
7108
|
+
mode = self.mode_selector.currentIndex()
|
|
7109
|
+
if mode == 0:
|
|
7103
7110
|
self.sort = 'Node'
|
|
7104
7111
|
else:
|
|
7105
|
-
mode = 1
|
|
7106
7112
|
self.sort = 'Edge'
|
|
7107
7113
|
|
|
7108
7114
|
|
|
@@ -7620,39 +7626,6 @@ class RadialDialog(QDialog):
|
|
|
7620
7626
|
except Exception as e:
|
|
7621
7627
|
print(f"An error occurred: {e}")
|
|
7622
7628
|
|
|
7623
|
-
class DegreeDistDialog(QDialog):
|
|
7624
|
-
|
|
7625
|
-
def __init__(self, parent=None):
|
|
7626
|
-
|
|
7627
|
-
super().__init__(parent)
|
|
7628
|
-
self.setWindowTitle("Degree Distribution Parameters")
|
|
7629
|
-
self.setModal(True)
|
|
7630
|
-
|
|
7631
|
-
layout = QFormLayout(self)
|
|
7632
|
-
|
|
7633
|
-
self.directory = QLineEdit("")
|
|
7634
|
-
layout.addRow("Output Directory:", self.directory)
|
|
7635
|
-
|
|
7636
|
-
# Add Run button
|
|
7637
|
-
run_button = QPushButton("Get Degree Distribution")
|
|
7638
|
-
run_button.clicked.connect(self.degreedist)
|
|
7639
|
-
layout.addWidget(run_button)
|
|
7640
|
-
|
|
7641
|
-
def degreedist(self):
|
|
7642
|
-
|
|
7643
|
-
try:
|
|
7644
|
-
|
|
7645
|
-
directory = str(self.distance.text()) if self.directory.text().strip() else None
|
|
7646
|
-
|
|
7647
|
-
degrees = my_network.degree_distribution(directory = directory)
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
self.parent().format_for_upperright_table(degrees, 'Degree (k)', 'Proportion of nodes with degree (p(k))', title = 'Degree Distribution Analysis')
|
|
7651
|
-
|
|
7652
|
-
self.accept()
|
|
7653
|
-
|
|
7654
|
-
except Exception as e:
|
|
7655
|
-
print(f"An error occurred: {e}")
|
|
7656
7629
|
|
|
7657
7630
|
class NearNeighDialog(QDialog):
|
|
7658
7631
|
def __init__(self, parent=None):
|
|
@@ -7687,9 +7660,12 @@ class NearNeighDialog(QDialog):
|
|
|
7687
7660
|
self.num = QLineEdit("1")
|
|
7688
7661
|
identities_layout.addRow("Number of Nearest Neighbors to Evaluate Per Node?:", self.num)
|
|
7689
7662
|
|
|
7690
|
-
|
|
7691
|
-
|
|
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)
|
|
7692
7667
|
|
|
7668
|
+
main_layout.addWidget(identities_group)
|
|
7693
7669
|
|
|
7694
7670
|
# Optional Heatmap group box
|
|
7695
7671
|
heatmap_group = QGroupBox("Optional Heatmap")
|
|
@@ -7723,39 +7699,71 @@ class NearNeighDialog(QDialog):
|
|
|
7723
7699
|
|
|
7724
7700
|
main_layout.addWidget(quant_group)
|
|
7725
7701
|
|
|
7726
|
-
# Get Distribution group box
|
|
7702
|
+
# Get Distribution group box - ENHANCED STYLING
|
|
7727
7703
|
distribution_group = QGroupBox("Get Distribution")
|
|
7728
7704
|
distribution_layout = QVBoxLayout(distribution_group)
|
|
7729
7705
|
|
|
7730
|
-
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
|
+
""")
|
|
7731
7725
|
run_button.clicked.connect(self.run)
|
|
7732
7726
|
distribution_layout.addWidget(run_button)
|
|
7733
7727
|
|
|
7734
7728
|
main_layout.addWidget(distribution_group)
|
|
7735
7729
|
|
|
7736
|
-
# Get All Averages group box (only if node_identities exists)
|
|
7730
|
+
# Get All Averages group box - ENHANCED STYLING (only if node_identities exists)
|
|
7737
7731
|
if my_network.node_identities is not None:
|
|
7738
7732
|
averages_group = QGroupBox("Get All Averages")
|
|
7739
7733
|
averages_layout = QVBoxLayout(averages_group)
|
|
7740
7734
|
|
|
7741
|
-
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
|
+
""")
|
|
7742
7755
|
run_button2.clicked.connect(self.run2)
|
|
7743
7756
|
averages_layout.addWidget(run_button2)
|
|
7744
7757
|
|
|
7745
7758
|
main_layout.addWidget(averages_group)
|
|
7746
7759
|
|
|
7747
7760
|
def toggle_map(self):
|
|
7748
|
-
|
|
7749
7761
|
if self.numpy.isChecked():
|
|
7750
|
-
|
|
7751
7762
|
if not self.map.isChecked():
|
|
7752
|
-
|
|
7753
7763
|
self.map.click()
|
|
7754
7764
|
|
|
7755
7765
|
def run(self):
|
|
7756
|
-
|
|
7757
7766
|
try:
|
|
7758
|
-
|
|
7759
7767
|
try:
|
|
7760
7768
|
root = self.root.currentText()
|
|
7761
7769
|
except:
|
|
@@ -7770,6 +7778,9 @@ class NearNeighDialog(QDialog):
|
|
|
7770
7778
|
numpy = self.numpy.isChecked()
|
|
7771
7779
|
num = int(self.num.text()) if self.num.text().strip() else 1
|
|
7772
7780
|
quant = self.quant.isChecked()
|
|
7781
|
+
centroids = self.centroids.isChecked()
|
|
7782
|
+
if not centroids:
|
|
7783
|
+
num = 1
|
|
7773
7784
|
|
|
7774
7785
|
if root is not None and targ is not None:
|
|
7775
7786
|
title = f"Nearest {num} Neighbor(s) Distance of {targ} from {root}"
|
|
@@ -7780,15 +7791,15 @@ class NearNeighDialog(QDialog):
|
|
|
7780
7791
|
header = f"Shortest Distance to Closest {num} Nodes"
|
|
7781
7792
|
header2 = "Root Node ID"
|
|
7782
7793
|
|
|
7783
|
-
if my_network.node_centroids is None:
|
|
7794
|
+
if centroids and my_network.node_centroids is None:
|
|
7784
7795
|
self.parent().show_centroid_dialog()
|
|
7785
7796
|
if my_network.node_centroids is None:
|
|
7786
7797
|
return
|
|
7787
7798
|
|
|
7788
7799
|
if not numpy:
|
|
7789
|
-
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)
|
|
7790
7801
|
else:
|
|
7791
|
-
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)
|
|
7792
7803
|
self.parent().load_channel(3, overlay, data = True)
|
|
7793
7804
|
|
|
7794
7805
|
if quant_overlay is not None:
|
|
@@ -7802,27 +7813,24 @@ class NearNeighDialog(QDialog):
|
|
|
7802
7813
|
except Exception as e:
|
|
7803
7814
|
import traceback
|
|
7804
7815
|
print(traceback.format_exc())
|
|
7805
|
-
|
|
7806
7816
|
print(f"Error: {e}")
|
|
7807
7817
|
|
|
7808
7818
|
def run2(self):
|
|
7809
|
-
|
|
7810
7819
|
try:
|
|
7811
|
-
|
|
7812
7820
|
available = list(set(my_network.node_identities.values()))
|
|
7813
|
-
|
|
7814
7821
|
num = int(self.num.text()) if self.num.text().strip() else 1
|
|
7815
7822
|
|
|
7823
|
+
centroids = self.centroids.isChecked()
|
|
7824
|
+
if not centroids:
|
|
7825
|
+
num = 1
|
|
7826
|
+
|
|
7816
7827
|
output_dict = {}
|
|
7817
7828
|
|
|
7818
7829
|
while len(available) > 1:
|
|
7819
|
-
|
|
7820
7830
|
root = available[0]
|
|
7821
7831
|
|
|
7822
7832
|
for targ in available:
|
|
7823
|
-
|
|
7824
|
-
avg, _, _ = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num)
|
|
7825
|
-
|
|
7833
|
+
avg, _, _ = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, centroids = centroids)
|
|
7826
7834
|
output_dict[f"{root} vs {targ}"] = avg
|
|
7827
7835
|
|
|
7828
7836
|
del available[0]
|
|
@@ -7832,7 +7840,6 @@ class NearNeighDialog(QDialog):
|
|
|
7832
7840
|
self.accept()
|
|
7833
7841
|
|
|
7834
7842
|
except Exception as e:
|
|
7835
|
-
|
|
7836
7843
|
print(f"Error: {e}")
|
|
7837
7844
|
|
|
7838
7845
|
|
|
@@ -9338,11 +9345,13 @@ class ThresholdDialog(QDialog):
|
|
|
9338
9345
|
|
|
9339
9346
|
def start_ml(self, GPU = False):
|
|
9340
9347
|
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9348
|
+
if self.parent().channel_data[0] is None:
|
|
9349
|
+
|
|
9350
|
+
try:
|
|
9351
|
+
print("Please select image to load into nodes channel for segmentation or press X if you already have the one you want. Note that this load may permit a color image in the nodes channel for segmentation purposes only, which is otherwise not allowed.")
|
|
9352
|
+
self.parent().load_channel(0, color = True)
|
|
9353
|
+
except:
|
|
9354
|
+
pass
|
|
9346
9355
|
|
|
9347
9356
|
|
|
9348
9357
|
if self.parent().channel_data[2] is not None or self.parent().channel_data[3] is not None or self.parent().highlight_overlay is not None:
|
|
@@ -9589,14 +9598,8 @@ class MachineWindow(QMainWindow):
|
|
|
9589
9598
|
train_quick.clicked.connect(lambda: self.train_model(speed=True))
|
|
9590
9599
|
train_detailed = QPushButton("Train Detailed Model (For Morphology)")
|
|
9591
9600
|
train_detailed.clicked.connect(lambda: self.train_model(speed=False))
|
|
9592
|
-
save = QPushButton("Save Model")
|
|
9593
|
-
save.clicked.connect(self.save_model)
|
|
9594
|
-
load = QPushButton("Load Model")
|
|
9595
|
-
load.clicked.connect(self.load_model)
|
|
9596
9601
|
training_layout.addWidget(train_quick)
|
|
9597
9602
|
training_layout.addWidget(train_detailed)
|
|
9598
|
-
training_layout.addWidget(save)
|
|
9599
|
-
training_layout.addWidget(load)
|
|
9600
9603
|
training_group.setLayout(training_layout)
|
|
9601
9604
|
|
|
9602
9605
|
# Group 4: Segmentation Options
|
|
@@ -9621,12 +9624,27 @@ class MachineWindow(QMainWindow):
|
|
|
9621
9624
|
segmentation_layout.addWidget(full_button)
|
|
9622
9625
|
segmentation_group.setLayout(segmentation_layout)
|
|
9623
9626
|
|
|
9627
|
+
# Group 5: Loading Options
|
|
9628
|
+
loading_group = QGroupBox("Saving/Loading")
|
|
9629
|
+
loading_layout = QHBoxLayout()
|
|
9630
|
+
self.save = QPushButton("Save Model")
|
|
9631
|
+
self.save.clicked.connect(self.save_model)
|
|
9632
|
+
self.load = QPushButton("Load Model")
|
|
9633
|
+
self.load.clicked.connect(self.load_model)
|
|
9634
|
+
load_nodes = QPushButton("Load Image (For Seg - Supports Color Images)")
|
|
9635
|
+
load_nodes.clicked.connect(self.load_nodes)
|
|
9636
|
+
loading_layout.addWidget(self.save)
|
|
9637
|
+
loading_layout.addWidget(self.load)
|
|
9638
|
+
loading_layout.addWidget(load_nodes)
|
|
9639
|
+
loading_group.setLayout(loading_layout)
|
|
9640
|
+
|
|
9624
9641
|
# Add all groups to main layout
|
|
9625
9642
|
main_layout.addWidget(drawing_group)
|
|
9626
9643
|
if not GPU:
|
|
9627
9644
|
main_layout.addWidget(processing_group)
|
|
9628
9645
|
main_layout.addWidget(training_group)
|
|
9629
9646
|
main_layout.addWidget(segmentation_group)
|
|
9647
|
+
main_layout.addWidget(loading_group)
|
|
9630
9648
|
|
|
9631
9649
|
# Set the main widget as the central widget
|
|
9632
9650
|
self.setCentralWidget(main_widget)
|
|
@@ -9649,6 +9667,79 @@ class MachineWindow(QMainWindow):
|
|
|
9649
9667
|
except:
|
|
9650
9668
|
return
|
|
9651
9669
|
|
|
9670
|
+
def load_nodes(self):
|
|
9671
|
+
|
|
9672
|
+
def confirm_machine_dialog():
|
|
9673
|
+
"""Shows a dialog asking user to confirm if they want to start the segmenter"""
|
|
9674
|
+
msg = QMessageBox()
|
|
9675
|
+
msg.setIcon(QMessageBox.Icon.Question)
|
|
9676
|
+
msg.setText("Alert")
|
|
9677
|
+
msg.setInformativeText("Use of this feature will require use of both overlay channels and the highlight overlay. Please save any data and return, or proceed if you do not need those overlays")
|
|
9678
|
+
msg.setWindowTitle("Proceed?")
|
|
9679
|
+
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
9680
|
+
return msg.exec() == QMessageBox.StandardButton.Yes
|
|
9681
|
+
|
|
9682
|
+
if self.parent().channel_data[2] is not None or self.parent().channel_data[3] is not None or self.parent().highlight_overlay is not None:
|
|
9683
|
+
if confirm_machine_dialog():
|
|
9684
|
+
pass
|
|
9685
|
+
else:
|
|
9686
|
+
return
|
|
9687
|
+
|
|
9688
|
+
try:
|
|
9689
|
+
|
|
9690
|
+
try:
|
|
9691
|
+
print("Please select image to load into nodes channel for segmentation or press X if you already have the one you want. Note that this load may permit a color image in the nodes channel for segmentation purposes only, which is otherwise not allowed.")
|
|
9692
|
+
self.parent().reset(nodes = True, edges = True, search_region = True, network_overlay = True, id_overlay = True)
|
|
9693
|
+
self.parent().highlight_overlay = None
|
|
9694
|
+
self.parent().load_channel(0, color = True)
|
|
9695
|
+
if self.parent().active_channel == 0:
|
|
9696
|
+
if self.parent().channel_data[0] is not None:
|
|
9697
|
+
try:
|
|
9698
|
+
active_data = self.parent().channel_data[0]
|
|
9699
|
+
act_channel = 0
|
|
9700
|
+
except:
|
|
9701
|
+
active_data = self.parent().channel_data[1]
|
|
9702
|
+
act_channel = 1
|
|
9703
|
+
import traceback
|
|
9704
|
+
traceback.print_exc()
|
|
9705
|
+
else:
|
|
9706
|
+
active_data = self.parent().channel_data[1]
|
|
9707
|
+
act_channel = 1
|
|
9708
|
+
|
|
9709
|
+
try:
|
|
9710
|
+
if len(active_data.shape) == 3:
|
|
9711
|
+
array1 = np.zeros_like(active_data).astype(np.uint8)
|
|
9712
|
+
elif len(active_data.shape) == 4:
|
|
9713
|
+
array1 = np.zeros_like(active_data)[:,:,:,0].astype(np.uint8)
|
|
9714
|
+
except:
|
|
9715
|
+
print("No data in nodes channel")
|
|
9716
|
+
import traceback
|
|
9717
|
+
traceback.print_exc()
|
|
9718
|
+
return
|
|
9719
|
+
array3 = np.zeros_like(array1).astype(np.uint8)
|
|
9720
|
+
self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
|
|
9721
|
+
|
|
9722
|
+
self.parent().load_channel(2, array1, True)
|
|
9723
|
+
self.trained = False
|
|
9724
|
+
self.previewing = False
|
|
9725
|
+
|
|
9726
|
+
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=False)
|
|
9727
|
+
|
|
9728
|
+
self.segmentation_worker = None
|
|
9729
|
+
|
|
9730
|
+
self.fore_button.click()
|
|
9731
|
+
self.fore_button.click()
|
|
9732
|
+
|
|
9733
|
+
self.num_chunks = 0
|
|
9734
|
+
self.parent().update_display()
|
|
9735
|
+
except:
|
|
9736
|
+
import traceback
|
|
9737
|
+
traceback.print_exc()
|
|
9738
|
+
pass
|
|
9739
|
+
|
|
9740
|
+
except:
|
|
9741
|
+
pass
|
|
9742
|
+
|
|
9652
9743
|
def toggle_segment(self):
|
|
9653
9744
|
|
|
9654
9745
|
if self.segmentation_worker is not None:
|
|
@@ -9850,10 +9941,8 @@ class MachineWindow(QMainWindow):
|
|
|
9850
9941
|
self.kill_segmentation()
|
|
9851
9942
|
time.sleep(0.1)
|
|
9852
9943
|
|
|
9853
|
-
if self.
|
|
9854
|
-
|
|
9855
|
-
else:
|
|
9856
|
-
print("Beginning new segmentation...")
|
|
9944
|
+
if not self.trained:
|
|
9945
|
+
return
|
|
9857
9946
|
|
|
9858
9947
|
if self.parent().channel_data[2] is not None:
|
|
9859
9948
|
active_data = self.parent().channel_data[2]
|
|
@@ -9863,19 +9952,16 @@ class MachineWindow(QMainWindow):
|
|
|
9863
9952
|
array3 = np.zeros_like(active_data).astype(np.uint8)
|
|
9864
9953
|
self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
|
|
9865
9954
|
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
x, y = 0, 0
|
|
9877
|
-
self.segmenter.update_position(self.parent().current_slice, x, y)
|
|
9878
|
-
self.segmentation_worker.start()
|
|
9955
|
+
self.segmentation_worker = SegmentationWorker(self.parent().highlight_overlay, self.segmenter, self.use_gpu, self.use_two, self.previewing, self, self.mem_lock)
|
|
9956
|
+
self.segmentation_worker.chunk_processed.connect(self.update_display) # Just update display
|
|
9957
|
+
current_xlim = self.parent().ax.get_xlim()
|
|
9958
|
+
current_ylim = self.parent().ax.get_ylim()
|
|
9959
|
+
try:
|
|
9960
|
+
x, y = self.parent().get_current_mouse_position()
|
|
9961
|
+
except:
|
|
9962
|
+
x, y = 0, 0
|
|
9963
|
+
self.segmenter.update_position(self.parent().current_slice, x, y)
|
|
9964
|
+
self.segmentation_worker.start()
|
|
9879
9965
|
|
|
9880
9966
|
def confirm_seg_dialog(self):
|
|
9881
9967
|
"""Shows a dialog asking user to confirm segment all"""
|
|
@@ -10086,7 +10172,10 @@ class SegmentationWorker(QThread):
|
|
|
10086
10172
|
self.mem_lock = mem_lock
|
|
10087
10173
|
self._stop = False
|
|
10088
10174
|
self._paused = False # Add pause flag
|
|
10089
|
-
self.
|
|
10175
|
+
if self.machine_window.parent().shape[1] * self.machine_window.parent().shape[2] > 3000 * 3000: #arbitrary throttle for large arrays.
|
|
10176
|
+
self.update_interval = 10
|
|
10177
|
+
else:
|
|
10178
|
+
self.update_interval = 1 # Increased to 1s
|
|
10090
10179
|
self.chunks_since_update = 0
|
|
10091
10180
|
self.chunks_per_update = 5 # Only update every 5 chunks
|
|
10092
10181
|
self.poked = False # If it should wake up or not
|
|
@@ -10138,13 +10227,20 @@ class SegmentationWorker(QThread):
|
|
|
10138
10227
|
current_time = time.time()
|
|
10139
10228
|
if (self.chunks_since_update >= self.chunks_per_update and
|
|
10140
10229
|
current_time - self.last_update >= self.update_interval):
|
|
10141
|
-
if self.machine_window.parent().shape[1] * self.machine_window.parent().shape[2] > 3000 * 3000: #arbitrary throttle for large arrays.
|
|
10142
|
-
self.msleep(3000)
|
|
10230
|
+
#if self.machine_window.parent().shape[1] * self.machine_window.parent().shape[2] > 3000 * 3000: #arbitrary throttle for large arrays.
|
|
10231
|
+
#self.msleep(3000)
|
|
10143
10232
|
self.chunk_processed.emit()
|
|
10144
10233
|
self.chunks_since_update = 0
|
|
10145
10234
|
self.last_update = current_time
|
|
10146
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
|
+
|
|
10147
10242
|
self.finished.emit()
|
|
10243
|
+
|
|
10148
10244
|
|
|
10149
10245
|
except Exception as e:
|
|
10150
10246
|
print(f"Error in segmentation: {e}")
|
|
@@ -11211,7 +11307,7 @@ class SkeletonizeDialog(QDialog):
|
|
|
11211
11307
|
)
|
|
11212
11308
|
|
|
11213
11309
|
if remove > 0:
|
|
11214
|
-
result = n3d.
|
|
11310
|
+
result = n3d.remove_branches_new(result, remove)
|
|
11215
11311
|
|
|
11216
11312
|
|
|
11217
11313
|
# Update both the display data and the network object
|
|
@@ -13008,6 +13104,409 @@ class ProxDialog(QDialog):
|
|
|
13008
13104
|
print(traceback.format_exc())
|
|
13009
13105
|
|
|
13010
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
|
+
|
|
13011
13510
|
|
|
13012
13511
|
# Initiating this program from the script line:
|
|
13013
13512
|
|