nettracer3d 0.7.3__py3-none-any.whl → 0.7.5__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/community_extractor.py +17 -39
- nettracer3d/modularity.py +41 -264
- nettracer3d/nettracer.py +31 -99
- nettracer3d/nettracer_gui.py +506 -271
- nettracer3d/network_analysis.py +0 -178
- nettracer3d/segmenter.py +84 -724
- nettracer3d/segmenter_GPU.py +1286 -0
- nettracer3d/simple_network.py +2 -150
- {nettracer3d-0.7.3.dist-info → nettracer3d-0.7.5.dist-info}/METADATA +8 -7
- nettracer3d-0.7.5.dist-info/RECORD +21 -0
- {nettracer3d-0.7.3.dist-info → nettracer3d-0.7.5.dist-info}/WHEEL +1 -1
- nettracer3d-0.7.3.dist-info/RECORD +0 -20
- {nettracer3d-0.7.3.dist-info → nettracer3d-0.7.5.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.7.3.dist-info → nettracer3d-0.7.5.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.7.3.dist-info → nettracer3d-0.7.5.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import networkx as nx
|
|
3
|
-
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
|
3
|
+
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QGridLayout,
|
|
4
4
|
QHBoxLayout, QSlider, QMenuBar, QMenu, QDialog,
|
|
5
5
|
QFormLayout, QLineEdit, QPushButton, QFileDialog,
|
|
6
6
|
QLabel, QComboBox, QMessageBox, QTableView, QInputDialog,
|
|
@@ -25,7 +25,7 @@ import multiprocessing as mp
|
|
|
25
25
|
from concurrent.futures import ThreadPoolExecutor
|
|
26
26
|
from functools import partial
|
|
27
27
|
from nettracer3d import segmenter
|
|
28
|
-
|
|
28
|
+
from nettracer3d import segmenter_GPU
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
|
|
@@ -1311,22 +1311,40 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1311
1311
|
info_dict['Object Class'] = 'Node'
|
|
1312
1312
|
|
|
1313
1313
|
if my_network.node_identities is not None:
|
|
1314
|
-
|
|
1314
|
+
try:
|
|
1315
|
+
info_dict['ID'] = my_network.node_identities[label]
|
|
1316
|
+
except:
|
|
1317
|
+
pass
|
|
1315
1318
|
|
|
1316
1319
|
if my_network.network is not None:
|
|
1317
|
-
|
|
1320
|
+
try:
|
|
1321
|
+
info_dict['Degree'] = my_network.network.degree(label)
|
|
1322
|
+
except:
|
|
1323
|
+
pass
|
|
1318
1324
|
|
|
1319
1325
|
if my_network.communities is not None:
|
|
1320
|
-
|
|
1326
|
+
try:
|
|
1327
|
+
info_dict['Community'] = my_network.communities[label]
|
|
1328
|
+
except:
|
|
1329
|
+
pass
|
|
1321
1330
|
|
|
1322
1331
|
if my_network.node_centroids is not None:
|
|
1323
|
-
|
|
1332
|
+
try:
|
|
1333
|
+
info_dict['Centroid'] = my_network.node_centroids[label]
|
|
1334
|
+
except:
|
|
1335
|
+
pass
|
|
1324
1336
|
|
|
1325
1337
|
if self.volume_dict[0] is not None:
|
|
1326
|
-
|
|
1338
|
+
try:
|
|
1339
|
+
info_dict['Volume (Scaled)'] = self.volume_dict[0][label]
|
|
1340
|
+
except:
|
|
1341
|
+
pass
|
|
1327
1342
|
|
|
1328
1343
|
if self.radii_dict[0] is not None:
|
|
1329
|
-
|
|
1344
|
+
try:
|
|
1345
|
+
info_dict['Max Radius (Scaled)'] = self.radii_dict[0][label]
|
|
1346
|
+
except:
|
|
1347
|
+
pass
|
|
1330
1348
|
|
|
1331
1349
|
|
|
1332
1350
|
elif sort == 'edge':
|
|
@@ -1338,13 +1356,22 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1338
1356
|
info_dict['Object Class'] = 'Edge'
|
|
1339
1357
|
|
|
1340
1358
|
if my_network.edge_centroids is not None:
|
|
1341
|
-
|
|
1359
|
+
try:
|
|
1360
|
+
info_dict['Centroid'] = my_network.edge_centroids[label]
|
|
1361
|
+
except:
|
|
1362
|
+
pass
|
|
1342
1363
|
|
|
1343
1364
|
if self.volume_dict[1] is not None:
|
|
1344
|
-
|
|
1365
|
+
try:
|
|
1366
|
+
info_dict['Volume (Scaled)'] = self.volume_dict[1][label]
|
|
1367
|
+
except:
|
|
1368
|
+
pass
|
|
1345
1369
|
|
|
1346
1370
|
if self.radii_dict[1] is not None:
|
|
1347
|
-
|
|
1371
|
+
try:
|
|
1372
|
+
info_dict['~Radius (Scaled)'] = self.radii_dict[1][label]
|
|
1373
|
+
except:
|
|
1374
|
+
pass
|
|
1348
1375
|
|
|
1349
1376
|
self.format_for_upperright_table(info_dict, title = f'Info on Object')
|
|
1350
1377
|
|
|
@@ -1353,7 +1380,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1353
1380
|
|
|
1354
1381
|
|
|
1355
1382
|
|
|
1356
|
-
|
|
1357
1383
|
def handle_combine(self):
|
|
1358
1384
|
|
|
1359
1385
|
try:
|
|
@@ -1416,12 +1442,67 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1416
1442
|
print(f"An error has occured: {e}")
|
|
1417
1443
|
|
|
1418
1444
|
def handle_seperate(self):
|
|
1419
|
-
|
|
1445
|
+
|
|
1446
|
+
import scipy.ndimage as ndi
|
|
1447
|
+
from scipy.sparse import csr_matrix
|
|
1448
|
+
|
|
1449
|
+
print("Note, this method is a tad slow...")
|
|
1450
|
+
|
|
1451
|
+
def separate_nontouching_objects(input_array):
|
|
1452
|
+
"""
|
|
1453
|
+
Efficiently separate non-touching objects in a labeled array.
|
|
1454
|
+
|
|
1455
|
+
Parameters:
|
|
1456
|
+
-----------
|
|
1457
|
+
input_array : numpy.ndarray
|
|
1458
|
+
Input labeled array where each object has a unique label value > 0
|
|
1459
|
+
|
|
1460
|
+
Returns:
|
|
1461
|
+
--------
|
|
1462
|
+
output_array : numpy.ndarray
|
|
1463
|
+
Array with new labels where non-touching components have different labels
|
|
1464
|
+
"""
|
|
1465
|
+
# Step 1: Perform connected component labeling on the entire binary mask
|
|
1466
|
+
binary_mask = input_array > 0
|
|
1467
|
+
structure = np.ones((3,) * input_array.ndim, dtype=bool) # 3x3x3 connectivity for 3D or 3x3 for 2D
|
|
1468
|
+
labeled_array, num_features = ndi.label(binary_mask, structure=structure)
|
|
1469
|
+
|
|
1470
|
+
# Step 2: Map the original labels to the new connected components
|
|
1471
|
+
# Create a sparse matrix to efficiently store label mappings
|
|
1472
|
+
coords = np.nonzero(input_array)
|
|
1473
|
+
original_values = input_array[coords]
|
|
1474
|
+
new_labels = labeled_array[coords]
|
|
1475
|
+
|
|
1476
|
+
# Create a mapping of (original_label, new_connected_component) pairs
|
|
1477
|
+
label_mapping = {}
|
|
1478
|
+
for orig, new in zip(original_values, new_labels):
|
|
1479
|
+
if orig not in label_mapping:
|
|
1480
|
+
label_mapping[orig] = []
|
|
1481
|
+
if new not in label_mapping[orig]:
|
|
1482
|
+
label_mapping[orig].append(new)
|
|
1483
|
+
|
|
1484
|
+
# Step 3: Create a new output array with unique labels for each connected component
|
|
1485
|
+
output_array = np.zeros_like(input_array)
|
|
1486
|
+
next_label = 1
|
|
1487
|
+
|
|
1488
|
+
# Map of (original_label, connected_component) -> new_unique_label
|
|
1489
|
+
unique_label_map = {}
|
|
1490
|
+
|
|
1491
|
+
for orig_label, cc_list in label_mapping.items():
|
|
1492
|
+
for cc in cc_list:
|
|
1493
|
+
unique_label_map[(orig_label, cc)] = next_label
|
|
1494
|
+
# Create a mask for this original label and connected component
|
|
1495
|
+
mask = (input_array == orig_label) & (labeled_array == cc)
|
|
1496
|
+
# Assign the new unique label
|
|
1497
|
+
output_array[mask] = next_label
|
|
1498
|
+
next_label += 1
|
|
1499
|
+
|
|
1500
|
+
return output_array
|
|
1501
|
+
|
|
1420
1502
|
try:
|
|
1421
1503
|
# Handle nodes
|
|
1422
1504
|
if len(self.clicked_values['nodes']) > 0:
|
|
1423
1505
|
self.create_highlight_overlay(node_indices=self.clicked_values['nodes'])
|
|
1424
|
-
max_val = np.max(my_network.nodes) + 1
|
|
1425
1506
|
|
|
1426
1507
|
# Create a boolean mask for highlighted values
|
|
1427
1508
|
self.highlight_overlay = self.highlight_overlay != 0
|
|
@@ -1429,56 +1510,21 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1429
1510
|
# Create array with just the highlighted values
|
|
1430
1511
|
highlighted_nodes = self.highlight_overlay * my_network.nodes
|
|
1431
1512
|
|
|
1432
|
-
# Get
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
val_mask = my_network.nodes == val
|
|
1441
|
-
|
|
1442
|
-
# Create an array without this value
|
|
1443
|
-
temp = my_network.nodes - (val_mask * val)
|
|
1444
|
-
|
|
1445
|
-
# Label the connected components for this value
|
|
1446
|
-
labeled_mask, num_components = n3d.label_objects(val_mask)
|
|
1447
|
-
|
|
1448
|
-
if num_components > 1:
|
|
1449
|
-
# Set appropriate dtype based on max value
|
|
1450
|
-
if max_val + num_components < 256:
|
|
1451
|
-
dtype = np.uint8
|
|
1452
|
-
elif max_val + num_components < 65536:
|
|
1453
|
-
dtype = np.uint16
|
|
1454
|
-
labeled_mask = labeled_mask.astype(dtype)
|
|
1455
|
-
temp = temp.astype(dtype)
|
|
1456
|
-
else:
|
|
1457
|
-
dtype = np.uint32
|
|
1458
|
-
labeled_mask = labeled_mask.astype(dtype)
|
|
1459
|
-
temp = temp.astype(dtype)
|
|
1460
|
-
|
|
1461
|
-
# Add new labels to the temporary array
|
|
1462
|
-
mask_nonzero = labeled_mask != 0
|
|
1463
|
-
labeled_mask = labeled_mask + max_val - 1 # -1 because we'll restore the first component
|
|
1464
|
-
labeled_mask = labeled_mask * mask_nonzero
|
|
1465
|
-
|
|
1466
|
-
# Restore original value for first component
|
|
1467
|
-
first_component = labeled_mask == max_val
|
|
1468
|
-
labeled_mask = labeled_mask - (first_component * (max_val - val))
|
|
1469
|
-
|
|
1470
|
-
# Add labeled components back to the array
|
|
1471
|
-
my_network.nodes = temp + labeled_mask
|
|
1472
|
-
|
|
1473
|
-
# Update max value for next iteration
|
|
1474
|
-
max_val += num_components - 1 # -1 because we kept one original label
|
|
1513
|
+
# Get non-highlighted part of the array
|
|
1514
|
+
non_highlighted = my_network.nodes * (~self.highlight_overlay)
|
|
1515
|
+
|
|
1516
|
+
# Process highlighted part
|
|
1517
|
+
processed_highlights = separate_nontouching_objects(highlighted_nodes)
|
|
1518
|
+
|
|
1519
|
+
# Combine back with non-highlighted parts
|
|
1520
|
+
my_network.nodes = non_highlighted + processed_highlights
|
|
1475
1521
|
|
|
1476
1522
|
self.load_channel(0, my_network.nodes, True)
|
|
1477
1523
|
|
|
1478
1524
|
# Handle edges
|
|
1479
1525
|
if len(self.clicked_values['edges']) > 0:
|
|
1526
|
+
|
|
1480
1527
|
self.create_highlight_overlay(edge_indices=self.clicked_values['edges'])
|
|
1481
|
-
max_val = np.max(my_network.edges) + 1
|
|
1482
1528
|
|
|
1483
1529
|
# Create a boolean mask for highlighted values
|
|
1484
1530
|
self.highlight_overlay = self.highlight_overlay != 0
|
|
@@ -1486,52 +1532,17 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1486
1532
|
# Create array with just the highlighted values
|
|
1487
1533
|
highlighted_edges = self.highlight_overlay * my_network.edges
|
|
1488
1534
|
|
|
1489
|
-
# Get
|
|
1490
|
-
|
|
1491
|
-
if vals[0] == 0:
|
|
1492
|
-
del vals[0]
|
|
1493
|
-
|
|
1494
|
-
# Process each value separately
|
|
1495
|
-
for val in vals:
|
|
1496
|
-
# Create a mask for this value
|
|
1497
|
-
val_mask = my_network.edges == val
|
|
1498
|
-
|
|
1499
|
-
# Create an array without this value
|
|
1500
|
-
temp = my_network.edges - (val_mask * val)
|
|
1501
|
-
|
|
1502
|
-
# Label the connected components for this value
|
|
1503
|
-
labeled_mask, num_components = n3d.label_objects(val_mask)
|
|
1504
|
-
|
|
1505
|
-
if num_components > 1:
|
|
1506
|
-
# Set appropriate dtype based on max value
|
|
1507
|
-
if max_val + num_components < 256:
|
|
1508
|
-
dtype = np.uint8
|
|
1509
|
-
elif max_val + num_components < 65536:
|
|
1510
|
-
dtype = np.uint16
|
|
1511
|
-
labeled_mask = labeled_mask.astype(dtype)
|
|
1512
|
-
temp = temp.astype(dtype)
|
|
1513
|
-
else:
|
|
1514
|
-
dtype = np.uint32
|
|
1515
|
-
labeled_mask = labeled_mask.astype(dtype)
|
|
1516
|
-
temp = temp.astype(dtype)
|
|
1517
|
-
|
|
1518
|
-
# Add new labels to the temporary array
|
|
1519
|
-
mask_nonzero = labeled_mask != 0
|
|
1520
|
-
labeled_mask = labeled_mask + max_val - 1 # -1 because we'll restore the first component
|
|
1521
|
-
labeled_mask = labeled_mask * mask_nonzero
|
|
1522
|
-
|
|
1523
|
-
# Restore original value for first component
|
|
1524
|
-
first_component = labeled_mask == max_val
|
|
1525
|
-
labeled_mask = labeled_mask - (first_component * (max_val - val))
|
|
1526
|
-
|
|
1527
|
-
# Add labeled components back to the array
|
|
1528
|
-
my_network.edges = temp + labeled_mask
|
|
1529
|
-
|
|
1530
|
-
# Update max value for next iteration
|
|
1531
|
-
max_val += num_components - 1 # -1 because we kept one original label
|
|
1535
|
+
# Get non-highlighted part of the array
|
|
1536
|
+
non_highlighted = my_network.edges * (~self.highlight_overlay)
|
|
1532
1537
|
|
|
1533
|
-
|
|
1538
|
+
# Process highlighted part
|
|
1539
|
+
processed_highlights = separate_nontouching_objects(highlighted_edges)
|
|
1534
1540
|
|
|
1541
|
+
# Combine back with non-highlighted parts
|
|
1542
|
+
my_network.edges = non_highlighted + processed_highlights
|
|
1543
|
+
|
|
1544
|
+
self.load_channel(1, my_network.edges, True)
|
|
1545
|
+
|
|
1535
1546
|
self.highlight_overlay = None
|
|
1536
1547
|
self.update_display()
|
|
1537
1548
|
print("Network is not updated automatically, please recompute if necessary. Identities are not automatically updated.")
|
|
@@ -1541,8 +1552,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1541
1552
|
|
|
1542
1553
|
|
|
1543
1554
|
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
1555
|
def handle_delete(self):
|
|
1547
1556
|
|
|
1548
1557
|
try:
|
|
@@ -2684,6 +2693,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2684
2693
|
stats_menu = analysis_menu.addMenu("Stats")
|
|
2685
2694
|
allstats_action = stats_menu.addAction("Calculate Generic Network Stats")
|
|
2686
2695
|
allstats_action.triggered.connect(self.stats)
|
|
2696
|
+
histos_action = stats_menu.addAction("Calculate Generic Network Histograms")
|
|
2697
|
+
histos_action.triggered.connect(self.histos)
|
|
2687
2698
|
radial_action = stats_menu.addAction("Radial Distribution Analysis")
|
|
2688
2699
|
radial_action.triggered.connect(self.show_radial_dialog)
|
|
2689
2700
|
degree_dist_action = stats_menu.addAction("Degree Distribution Analysis")
|
|
@@ -2822,6 +2833,104 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2822
2833
|
except Exception as e:
|
|
2823
2834
|
print(f"Error finding stats: {e}")
|
|
2824
2835
|
|
|
2836
|
+
def histos(self):
|
|
2837
|
+
|
|
2838
|
+
"""from networkx documentation"""
|
|
2839
|
+
|
|
2840
|
+
G = my_network.network
|
|
2841
|
+
|
|
2842
|
+
shortest_path_lengths = dict(nx.all_pairs_shortest_path_length(G))
|
|
2843
|
+
diameter = max(nx.eccentricity(G, sp=shortest_path_lengths).values())
|
|
2844
|
+
# We know the maximum shortest path length (the diameter), so create an array
|
|
2845
|
+
# to store values from 0 up to (and including) diameter
|
|
2846
|
+
path_lengths = np.zeros(diameter + 1, dtype=int)
|
|
2847
|
+
|
|
2848
|
+
|
|
2849
|
+
|
|
2850
|
+
# Extract the frequency of shortest path lengths between two nodes
|
|
2851
|
+
for pls in shortest_path_lengths.values():
|
|
2852
|
+
pl, cnts = np.unique(list(pls.values()), return_counts=True)
|
|
2853
|
+
path_lengths[pl] += cnts
|
|
2854
|
+
|
|
2855
|
+
# Express frequency distribution as a percentage (ignoring path lengths of 0)
|
|
2856
|
+
freq_percent = 100 * path_lengths[1:] / path_lengths[1:].sum()
|
|
2857
|
+
|
|
2858
|
+
# Plot the frequency distribution (ignoring path lengths of 0) as a percentage
|
|
2859
|
+
fig, ax = plt.subplots(figsize=(15, 8))
|
|
2860
|
+
ax.bar(np.arange(1, diameter + 1), height=freq_percent)
|
|
2861
|
+
ax.set_title(
|
|
2862
|
+
"Distribution of shortest path length in G", fontdict={"size": 35}, loc="center"
|
|
2863
|
+
)
|
|
2864
|
+
ax.set_xlabel("Shortest Path Length", fontdict={"size": 22})
|
|
2865
|
+
ax.set_ylabel("Frequency (%)", fontdict={"size": 22})
|
|
2866
|
+
|
|
2867
|
+
plt.show()
|
|
2868
|
+
freq_dict = {freq: length for length, freq in enumerate(freq_percent, start=1)}
|
|
2869
|
+
self.format_for_upperright_table(freq_dict, metric='Frequency (%)', value='Shortest Path Length', title="Distribution of shortest path length in G")
|
|
2870
|
+
|
|
2871
|
+
degree_centrality = nx.centrality.degree_centrality(G)
|
|
2872
|
+
plt.figure(figsize=(15, 8))
|
|
2873
|
+
plt.hist(degree_centrality.values(), bins=25)
|
|
2874
|
+
plt.xticks(ticks=[0, 0.025, 0.05, 0.1, 0.15, 0.2]) # set the x axis ticks
|
|
2875
|
+
plt.title("Degree Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
2876
|
+
plt.xlabel("Degree Centrality", fontdict={"size": 20})
|
|
2877
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2878
|
+
plt.show()
|
|
2879
|
+
self.format_for_upperright_table(degree_centrality, metric='Node', value='Degree Centrality', title="Degree Centrality Table")
|
|
2880
|
+
|
|
2881
|
+
|
|
2882
|
+
betweenness_centrality = nx.centrality.betweenness_centrality(
|
|
2883
|
+
G
|
|
2884
|
+
)
|
|
2885
|
+
plt.figure(figsize=(15, 8))
|
|
2886
|
+
plt.hist(betweenness_centrality.values(), bins=100)
|
|
2887
|
+
plt.xticks(ticks=[0, 0.02, 0.1, 0.2, 0.3, 0.4, 0.5]) # set the x axis ticks
|
|
2888
|
+
plt.title("Betweenness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
2889
|
+
plt.xlabel("Betweenness Centrality", fontdict={"size": 20})
|
|
2890
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2891
|
+
plt.show()
|
|
2892
|
+
self.format_for_upperright_table(betweenness_centrality, metric='Node', value='Betweenness Centrality', title="Betweenness Centrality Table")
|
|
2893
|
+
|
|
2894
|
+
|
|
2895
|
+
closeness_centrality = nx.centrality.closeness_centrality(
|
|
2896
|
+
G
|
|
2897
|
+
)
|
|
2898
|
+
plt.figure(figsize=(15, 8))
|
|
2899
|
+
plt.hist(closeness_centrality.values(), bins=60)
|
|
2900
|
+
plt.title("Closeness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
2901
|
+
plt.xlabel("Closeness Centrality", fontdict={"size": 20})
|
|
2902
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2903
|
+
plt.show()
|
|
2904
|
+
self.format_for_upperright_table(closeness_centrality, metric='Node', value='Closeness Centrality', title="Closeness Centrality Table")
|
|
2905
|
+
|
|
2906
|
+
|
|
2907
|
+
eigenvector_centrality = nx.centrality.eigenvector_centrality(
|
|
2908
|
+
G
|
|
2909
|
+
)
|
|
2910
|
+
plt.figure(figsize=(15, 8))
|
|
2911
|
+
plt.hist(eigenvector_centrality.values(), bins=60)
|
|
2912
|
+
plt.xticks(ticks=[0, 0.01, 0.02, 0.04, 0.06, 0.08]) # set the x axis ticks
|
|
2913
|
+
plt.title("Eigenvector Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
2914
|
+
plt.xlabel("Eigenvector Centrality", fontdict={"size": 20})
|
|
2915
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2916
|
+
plt.show()
|
|
2917
|
+
self.format_for_upperright_table(eigenvector_centrality, metric='Node', value='Eigenvector Centrality', title="Eigenvector Centrality Table")
|
|
2918
|
+
|
|
2919
|
+
|
|
2920
|
+
|
|
2921
|
+
clusters = nx.clustering(G)
|
|
2922
|
+
plt.figure(figsize=(15, 8))
|
|
2923
|
+
plt.hist(clusters.values(), bins=50)
|
|
2924
|
+
plt.title("Clustering Coefficient Histogram ", fontdict={"size": 35}, loc="center")
|
|
2925
|
+
plt.xlabel("Clustering Coefficient", fontdict={"size": 20})
|
|
2926
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2927
|
+
plt.show()
|
|
2928
|
+
self.format_for_upperright_table(clusters, metric='Node', value='Clustering Coefficient', title="Clustering Coefficient Table")
|
|
2929
|
+
|
|
2930
|
+
bridges = list(nx.bridges(G))
|
|
2931
|
+
self.format_for_upperright_table(bridges, metric = 'Node Pair', title="Bridges")
|
|
2932
|
+
|
|
2933
|
+
|
|
2825
2934
|
def volumes(self):
|
|
2826
2935
|
|
|
2827
2936
|
|
|
@@ -2870,67 +2979,72 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2870
2979
|
return val
|
|
2871
2980
|
return val
|
|
2872
2981
|
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
})
|
|
2878
|
-
|
|
2879
|
-
# Format floating point numbers
|
|
2880
|
-
df[metric] = df[metric].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
|
|
2881
|
-
|
|
2882
|
-
else: # Dictionary input
|
|
2883
|
-
# Get sample value to determine structure
|
|
2884
|
-
sample_value = next(iter(data.values()))
|
|
2885
|
-
is_multi_value = isinstance(sample_value, (list, tuple, np.ndarray))
|
|
2886
|
-
|
|
2887
|
-
if is_multi_value:
|
|
2888
|
-
# Handle multi-value case
|
|
2889
|
-
if isinstance(value, str):
|
|
2890
|
-
# If single string provided for multi-values, generate numbered headers
|
|
2891
|
-
n_cols = len(sample_value)
|
|
2892
|
-
value_headers = [f"{value}_{i+1}" for i in range(n_cols)]
|
|
2893
|
-
else:
|
|
2894
|
-
# Use provided list of headers
|
|
2895
|
-
value_headers = value
|
|
2896
|
-
if len(value_headers) != len(sample_value):
|
|
2897
|
-
raise ValueError("Number of headers must match number of values per key")
|
|
2898
|
-
|
|
2899
|
-
# Create lists for each column
|
|
2900
|
-
dict_data = {metric: list(data.keys())}
|
|
2901
|
-
for i, header in enumerate(value_headers):
|
|
2902
|
-
# Convert values to numeric when possible before adding to DataFrame
|
|
2903
|
-
dict_data[header] = [convert_to_numeric(data[key][i]) for key in data.keys()]
|
|
2904
|
-
|
|
2905
|
-
df = pd.DataFrame(dict_data)
|
|
2906
|
-
|
|
2907
|
-
# Format floating point numbers in all value columns
|
|
2908
|
-
for header in value_headers:
|
|
2909
|
-
df[header] = df[header].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
|
|
2910
|
-
|
|
2911
|
-
else:
|
|
2912
|
-
# Single-value case
|
|
2982
|
+
try:
|
|
2983
|
+
|
|
2984
|
+
if isinstance(data, (list, tuple, np.ndarray)):
|
|
2985
|
+
# Handle list input - create single column DataFrame
|
|
2913
2986
|
df = pd.DataFrame({
|
|
2914
|
-
metric:
|
|
2915
|
-
value: [convert_to_numeric(val) for val in data.values()]
|
|
2987
|
+
metric: [convert_to_numeric(val) for val in data]
|
|
2916
2988
|
})
|
|
2917
2989
|
|
|
2918
2990
|
# Format floating point numbers
|
|
2919
|
-
df[
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2991
|
+
df[metric] = df[metric].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
|
|
2992
|
+
|
|
2993
|
+
else: # Dictionary input
|
|
2994
|
+
# Get sample value to determine structure
|
|
2995
|
+
sample_value = next(iter(data.values()))
|
|
2996
|
+
is_multi_value = isinstance(sample_value, (list, tuple, np.ndarray))
|
|
2997
|
+
|
|
2998
|
+
if is_multi_value:
|
|
2999
|
+
# Handle multi-value case
|
|
3000
|
+
if isinstance(value, str):
|
|
3001
|
+
# If single string provided for multi-values, generate numbered headers
|
|
3002
|
+
n_cols = len(sample_value)
|
|
3003
|
+
value_headers = [f"{value}_{i+1}" for i in range(n_cols)]
|
|
3004
|
+
else:
|
|
3005
|
+
# Use provided list of headers
|
|
3006
|
+
value_headers = value
|
|
3007
|
+
if len(value_headers) != len(sample_value):
|
|
3008
|
+
raise ValueError("Number of headers must match number of values per key")
|
|
3009
|
+
|
|
3010
|
+
# Create lists for each column
|
|
3011
|
+
dict_data = {metric: list(data.keys())}
|
|
3012
|
+
for i, header in enumerate(value_headers):
|
|
3013
|
+
# Convert values to numeric when possible before adding to DataFrame
|
|
3014
|
+
dict_data[header] = [convert_to_numeric(data[key][i]) for key in data.keys()]
|
|
3015
|
+
|
|
3016
|
+
df = pd.DataFrame(dict_data)
|
|
3017
|
+
|
|
3018
|
+
# Format floating point numbers in all value columns
|
|
3019
|
+
for header in value_headers:
|
|
3020
|
+
df[header] = df[header].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
|
|
3021
|
+
|
|
3022
|
+
else:
|
|
3023
|
+
# Single-value case
|
|
3024
|
+
df = pd.DataFrame({
|
|
3025
|
+
metric: data.keys(),
|
|
3026
|
+
value: [convert_to_numeric(val) for val in data.values()]
|
|
3027
|
+
})
|
|
3028
|
+
|
|
3029
|
+
# Format floating point numbers
|
|
3030
|
+
df[value] = df[value].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
|
|
3031
|
+
|
|
3032
|
+
# Create new table
|
|
3033
|
+
table = CustomTableView(self)
|
|
3034
|
+
table.setModel(PandasModel(df))
|
|
3035
|
+
|
|
3036
|
+
# Add to tabbed widget
|
|
3037
|
+
if title is None:
|
|
3038
|
+
self.tabbed_data.add_table(f"{metric} Analysis", table)
|
|
3039
|
+
else:
|
|
3040
|
+
self.tabbed_data.add_table(f"{title}", table)
|
|
3041
|
+
|
|
3042
|
+
# Adjust column widths to content
|
|
3043
|
+
for column in range(table.model().columnCount(None)):
|
|
3044
|
+
table.resizeColumnToContents(column)
|
|
3045
|
+
|
|
3046
|
+
except:
|
|
3047
|
+
pass
|
|
2934
3048
|
|
|
2935
3049
|
|
|
2936
3050
|
def show_watershed_dialog(self):
|
|
@@ -5751,7 +5865,7 @@ class NetShowDialog(QDialog):
|
|
|
5751
5865
|
|
|
5752
5866
|
# Add mode selection dropdown
|
|
5753
5867
|
self.mode_selector = QComboBox()
|
|
5754
|
-
self.mode_selector.addItems(["Default", "Community Coded
|
|
5868
|
+
self.mode_selector.addItems(["Default", "Community Coded", "Node ID Coded"])
|
|
5755
5869
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
5756
5870
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
5757
5871
|
|
|
@@ -5773,6 +5887,10 @@ class NetShowDialog(QDialog):
|
|
|
5773
5887
|
|
|
5774
5888
|
def show_network(self):
|
|
5775
5889
|
# Get parameters and run analysis
|
|
5890
|
+
if my_network.communities is None:
|
|
5891
|
+
self.parent().show_partition_dialog()
|
|
5892
|
+
if my_network.communities is None:
|
|
5893
|
+
return
|
|
5776
5894
|
geo = self.geo_layout.isChecked()
|
|
5777
5895
|
if geo:
|
|
5778
5896
|
if my_network.node_centroids is None:
|
|
@@ -5790,12 +5908,6 @@ class NetShowDialog(QDialog):
|
|
|
5790
5908
|
my_network.show_communities_flex(geometric=geo, directory = directory, weighted = weighted, partition = my_network.communities)
|
|
5791
5909
|
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID')
|
|
5792
5910
|
elif accepted_mode == 2:
|
|
5793
|
-
my_network.show_communities_flex(geometric=geo, directory = directory, weighted = weighted, partition = my_network.communities, style = 0)
|
|
5794
|
-
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID')
|
|
5795
|
-
elif accepted_mode ==3:
|
|
5796
|
-
my_network.show_communities_flex(geometric=geo, directory = directory, weighted = weighted, partition = my_network.communities, style = 1)
|
|
5797
|
-
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID')
|
|
5798
|
-
elif accepted_mode == 4:
|
|
5799
5911
|
my_network.show_identity_network(geometric=geo, directory = directory)
|
|
5800
5912
|
|
|
5801
5913
|
self.accept()
|
|
@@ -5831,6 +5943,9 @@ class PartitionDialog(QDialog):
|
|
|
5831
5943
|
self.stats.setChecked(True)
|
|
5832
5944
|
layout.addRow("Community Stats:", self.stats)
|
|
5833
5945
|
|
|
5946
|
+
self.seed = QLineEdit("")
|
|
5947
|
+
layout.addRow("Seed (int):", self.seed)
|
|
5948
|
+
|
|
5834
5949
|
# Add Run button
|
|
5835
5950
|
run_button = QPushButton("Partition")
|
|
5836
5951
|
run_button.clicked.connect(self.partition)
|
|
@@ -5842,10 +5957,16 @@ class PartitionDialog(QDialog):
|
|
|
5842
5957
|
weighted = self.weighted.isChecked()
|
|
5843
5958
|
dostats = self.stats.isChecked()
|
|
5844
5959
|
|
|
5960
|
+
try:
|
|
5961
|
+
seed = int(self.seed.text()) if self.seed.text() else None
|
|
5962
|
+
except:
|
|
5963
|
+
seed = None
|
|
5964
|
+
|
|
5965
|
+
|
|
5845
5966
|
my_network.communities = None
|
|
5846
5967
|
|
|
5847
5968
|
try:
|
|
5848
|
-
stats = my_network.community_partition(weighted = weighted, style = accepted_mode, dostats = dostats)
|
|
5969
|
+
stats = my_network.community_partition(weighted = weighted, style = accepted_mode, dostats = dostats, seed = seed)
|
|
5849
5970
|
print(f"Discovered communities: {my_network.communities}")
|
|
5850
5971
|
|
|
5851
5972
|
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID', title = 'Community Partition')
|
|
@@ -6560,21 +6681,21 @@ class MotherDialog(QDialog):
|
|
|
6560
6681
|
|
|
6561
6682
|
overlay = self.overlay.isChecked()
|
|
6562
6683
|
|
|
6684
|
+
if my_network.communities is None:
|
|
6685
|
+
self.parent().show_partition_dialog()
|
|
6686
|
+
if my_network.communities is None:
|
|
6687
|
+
return
|
|
6688
|
+
|
|
6563
6689
|
if my_network.node_centroids is None:
|
|
6564
6690
|
self.parent().show_centroid_dialog()
|
|
6565
6691
|
if my_network.node_centroids is None:
|
|
6566
6692
|
print("Error finding centroids")
|
|
6567
6693
|
overlay = False
|
|
6568
6694
|
|
|
6569
|
-
if my_network.communities is None:
|
|
6570
|
-
self.parent().show_partition_dialog()
|
|
6571
|
-
if my_network.communities is None:
|
|
6572
|
-
return
|
|
6573
|
-
|
|
6574
6695
|
if not overlay:
|
|
6575
|
-
G = my_network.isolate_mothers(self,
|
|
6696
|
+
G = my_network.isolate_mothers(self, ret_nodes = True, called = True)
|
|
6576
6697
|
else:
|
|
6577
|
-
G, result = my_network.isolate_mothers(self,
|
|
6698
|
+
G, result = my_network.isolate_mothers(self, ret_nodes = False, called = True)
|
|
6578
6699
|
self.parent().load_channel(2, channel_data = result, data = True)
|
|
6579
6700
|
|
|
6580
6701
|
degree_dict = {}
|
|
@@ -7412,7 +7533,11 @@ class MachineWindow(QMainWindow):
|
|
|
7412
7533
|
self.three.setCheckable(True)
|
|
7413
7534
|
self.three.setChecked(True)
|
|
7414
7535
|
self.three.clicked.connect(self.toggle_three)
|
|
7415
|
-
|
|
7536
|
+
self.GPU = QPushButton("GPU")
|
|
7537
|
+
self.GPU.setCheckable(True)
|
|
7538
|
+
self.GPU.setChecked(False)
|
|
7539
|
+
self.GPU.clicked.connect(self.toggle_GPU)
|
|
7540
|
+
processing_layout.addWidget(self.GPU)
|
|
7416
7541
|
processing_layout.addWidget(self.two)
|
|
7417
7542
|
processing_layout.addWidget(self.three)
|
|
7418
7543
|
processing_group.setLayout(processing_layout)
|
|
@@ -7504,31 +7629,41 @@ class MachineWindow(QMainWindow):
|
|
|
7504
7629
|
|
|
7505
7630
|
def save_model(self):
|
|
7506
7631
|
|
|
7507
|
-
|
|
7508
|
-
self,
|
|
7509
|
-
f"Save Model As",
|
|
7510
|
-
"", # Default directory
|
|
7511
|
-
"numpy data (*.npz);;All Files (*)" # File type filter
|
|
7512
|
-
)
|
|
7513
|
-
|
|
7514
|
-
if filename: # Only proceed if user didn't cancel
|
|
7515
|
-
# If user didn't type an extension, add .tif
|
|
7516
|
-
if not filename.endswith(('.npz')):
|
|
7517
|
-
filename += '.npz'
|
|
7632
|
+
try:
|
|
7518
7633
|
|
|
7519
|
-
|
|
7634
|
+
filename, _ = QFileDialog.getSaveFileName(
|
|
7635
|
+
self,
|
|
7636
|
+
f"Save Model As",
|
|
7637
|
+
"", # Default directory
|
|
7638
|
+
"numpy data (*.npz);;All Files (*)" # File type filter
|
|
7639
|
+
)
|
|
7640
|
+
|
|
7641
|
+
if filename: # Only proceed if user didn't cancel
|
|
7642
|
+
# If user didn't type an extension, add .tif
|
|
7643
|
+
if not filename.endswith(('.npz')):
|
|
7644
|
+
filename += '.npz'
|
|
7645
|
+
|
|
7646
|
+
self.segmenter.save_model(filename, self.parent().channel_data[2])
|
|
7647
|
+
|
|
7648
|
+
except Exception as e:
|
|
7649
|
+
print(f"Error saving model: {e}")
|
|
7520
7650
|
|
|
7521
7651
|
def load_model(self):
|
|
7522
7652
|
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7653
|
+
try:
|
|
7654
|
+
|
|
7655
|
+
filename, _ = QFileDialog.getOpenFileName(
|
|
7656
|
+
self,
|
|
7657
|
+
f"Load Model",
|
|
7658
|
+
"",
|
|
7659
|
+
"numpy data (*.npz)"
|
|
7660
|
+
)
|
|
7661
|
+
|
|
7662
|
+
self.segmenter.load_model(filename)
|
|
7663
|
+
self.trained = True
|
|
7529
7664
|
|
|
7530
|
-
|
|
7531
|
-
|
|
7665
|
+
except Exception as e:
|
|
7666
|
+
print(f"Error loading model: {e}")
|
|
7532
7667
|
|
|
7533
7668
|
def toggle_two(self):
|
|
7534
7669
|
if self.two.isChecked():
|
|
@@ -7550,6 +7685,35 @@ class MachineWindow(QMainWindow):
|
|
|
7550
7685
|
self.two.setChecked(True)
|
|
7551
7686
|
self.use_two = True
|
|
7552
7687
|
|
|
7688
|
+
def toggle_GPU(self):
|
|
7689
|
+
|
|
7690
|
+
if self.parent().active_channel == 0:
|
|
7691
|
+
if self.parent().channel_data[0] is not None:
|
|
7692
|
+
try:
|
|
7693
|
+
active_data = self.parent().channel_data[0]
|
|
7694
|
+
act_channel = 0
|
|
7695
|
+
except:
|
|
7696
|
+
active_data = self.parent().channel_data[1]
|
|
7697
|
+
act_channel = 1
|
|
7698
|
+
else:
|
|
7699
|
+
active_data = self.parent().channel_data[1]
|
|
7700
|
+
act_channel = 1
|
|
7701
|
+
|
|
7702
|
+
if self.GPU.isChecked():
|
|
7703
|
+
|
|
7704
|
+
try:
|
|
7705
|
+
self.segmenter = segmenter_GPU.InteractiveSegmenter(active_data)
|
|
7706
|
+
print("Using GPU")
|
|
7707
|
+
except:
|
|
7708
|
+
self.GPU.setChecked(False)
|
|
7709
|
+
print("Could not detect GPU")
|
|
7710
|
+
import traceback
|
|
7711
|
+
traceback.print_exc()
|
|
7712
|
+
|
|
7713
|
+
else:
|
|
7714
|
+
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=False)
|
|
7715
|
+
print("Using CPU")
|
|
7716
|
+
|
|
7553
7717
|
|
|
7554
7718
|
|
|
7555
7719
|
def toggle_foreground(self):
|
|
@@ -7753,8 +7917,6 @@ class MachineWindow(QMainWindow):
|
|
|
7753
7917
|
traceback.print_exc()
|
|
7754
7918
|
|
|
7755
7919
|
def segmentation_finished(self):
|
|
7756
|
-
if not self.use_two:
|
|
7757
|
-
print("Segmentation completed")
|
|
7758
7920
|
|
|
7759
7921
|
current_xlim = self.parent().ax.get_xlim()
|
|
7760
7922
|
current_ylim = self.parent().ax.get_ylim()
|
|
@@ -7836,18 +7998,12 @@ class MachineWindow(QMainWindow):
|
|
|
7836
7998
|
self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
|
|
7837
7999
|
|
|
7838
8000
|
print("Segmenting entire volume with model...")
|
|
7839
|
-
foreground_coords, background_coords = self.segmenter.segment_volume(
|
|
8001
|
+
#foreground_coords, background_coords = self.segmenter.segment_volume(array = self.parent().highlight_overlay)
|
|
8002
|
+
self.parent().highlight_overlay = self.segmenter.segment_volume(array = self.parent().highlight_overlay)
|
|
7840
8003
|
|
|
7841
8004
|
# Clean up when done
|
|
7842
8005
|
self.segmenter.cleanup()
|
|
7843
8006
|
|
|
7844
|
-
fg_array = np.array(list(foreground_coords))
|
|
7845
|
-
if len(fg_array) > 0: # Check if we have any foreground coordinates
|
|
7846
|
-
# Unpack into separate coordinate arrays
|
|
7847
|
-
z_coords, y_coords, x_coords = fg_array[:, 0], fg_array[:, 1], fg_array[:, 2]
|
|
7848
|
-
# Assign values in a single vectorized operation
|
|
7849
|
-
self.parent().highlight_overlay[z_coords, y_coords, x_coords] = 255
|
|
7850
|
-
|
|
7851
8007
|
self.parent().load_channel(3, self.parent().highlight_overlay, True)
|
|
7852
8008
|
|
|
7853
8009
|
# Not exactly sure why we need all this but the channel buttons weren't loading like they normally do when load_channel() is called:
|
|
@@ -7962,7 +8118,7 @@ class SegmentationWorker(QThread):
|
|
|
7962
8118
|
current_time - self.last_update >= self.update_interval):
|
|
7963
8119
|
self.chunk_processed.emit()
|
|
7964
8120
|
self.chunks_since_update = 0
|
|
7965
|
-
self.last_update = current_time
|
|
8121
|
+
self.last_update = current_time
|
|
7966
8122
|
|
|
7967
8123
|
self.finished.emit()
|
|
7968
8124
|
|
|
@@ -9100,65 +9256,110 @@ class CentroidNodeDialog(QDialog):
|
|
|
9100
9256
|
|
|
9101
9257
|
class GenNodesDialog(QDialog):
|
|
9102
9258
|
|
|
9103
|
-
def __init__(self, parent=None, down_factor
|
|
9259
|
+
def __init__(self, parent=None, down_factor=None, called=False):
|
|
9104
9260
|
super().__init__(parent)
|
|
9105
9261
|
self.setWindowTitle("Create Nodes from Edge Vertices")
|
|
9106
9262
|
self.setModal(True)
|
|
9107
|
-
|
|
9108
|
-
|
|
9263
|
+
|
|
9264
|
+
# Main layout
|
|
9265
|
+
main_layout = QVBoxLayout(self)
|
|
9109
9266
|
self.called = called
|
|
9110
|
-
|
|
9111
|
-
#
|
|
9112
|
-
#self.directory.setPlaceholderText("Leave empty to save in active dir")
|
|
9113
|
-
#layout.addRow("Output Directory:", self.directory)
|
|
9114
|
-
|
|
9267
|
+
|
|
9268
|
+
# Set down_factor and cubic
|
|
9115
9269
|
if not down_factor:
|
|
9116
9270
|
down_factor = None
|
|
9271
|
+
|
|
9117
9272
|
if down_factor is None:
|
|
9273
|
+
# --- Processing Options Group ---
|
|
9274
|
+
process_group = QGroupBox("Processing Options")
|
|
9275
|
+
process_layout = QGridLayout()
|
|
9276
|
+
|
|
9277
|
+
# Downsample factor
|
|
9118
9278
|
self.down_factor = QLineEdit("0")
|
|
9119
|
-
|
|
9279
|
+
process_layout.addWidget(QLabel("Downsample Factor (Speeds up calculation at the cost of fidelity):"), 0, 0)
|
|
9280
|
+
process_layout.addWidget(self.down_factor, 0, 1)
|
|
9281
|
+
|
|
9282
|
+
# Cubic checkbox
|
|
9120
9283
|
self.cubic = QPushButton("Cubic Downsample")
|
|
9121
9284
|
self.cubic.setCheckable(True)
|
|
9122
9285
|
self.cubic.setChecked(False)
|
|
9123
|
-
|
|
9286
|
+
process_layout.addWidget(QLabel("Use cubic downsample? (Slower but preserves structure better):"), 1, 0)
|
|
9287
|
+
process_layout.addWidget(self.cubic, 1, 1)
|
|
9288
|
+
|
|
9289
|
+
# Fast dilation checkbox
|
|
9290
|
+
self.fast_dil = QPushButton("Fast-Dil")
|
|
9291
|
+
self.fast_dil.setCheckable(True)
|
|
9292
|
+
self.fast_dil.setChecked(True)
|
|
9293
|
+
process_layout.addWidget(QLabel("Use Fast Dilation (Higher speed, less accurate with large search regions):"), 2, 0)
|
|
9294
|
+
process_layout.addWidget(self.fast_dil, 2, 1)
|
|
9295
|
+
|
|
9296
|
+
process_group.setLayout(process_layout)
|
|
9297
|
+
main_layout.addWidget(process_group)
|
|
9124
9298
|
else:
|
|
9125
9299
|
self.down_factor = down_factor[0]
|
|
9126
9300
|
self.cubic = down_factor[1]
|
|
9127
|
-
|
|
9301
|
+
|
|
9302
|
+
# Fast dilation checkbox (still needed even if down_factor is provided)
|
|
9303
|
+
process_group = QGroupBox("Processing Options")
|
|
9304
|
+
process_layout = QGridLayout()
|
|
9305
|
+
|
|
9306
|
+
self.fast_dil = QPushButton("Fast-Dil")
|
|
9307
|
+
self.fast_dil.setCheckable(True)
|
|
9308
|
+
self.fast_dil.setChecked(True)
|
|
9309
|
+
process_layout.addWidget(QLabel("Use Fast Dilation (Higher speed, less accurate with large search regions):"), 0, 0)
|
|
9310
|
+
process_layout.addWidget(self.fast_dil, 0, 1)
|
|
9311
|
+
|
|
9312
|
+
process_group.setLayout(process_layout)
|
|
9313
|
+
main_layout.addWidget(process_group)
|
|
9314
|
+
|
|
9315
|
+
# --- Recommended Corrections Group ---
|
|
9316
|
+
rec_group = QGroupBox("Recommended Corrections")
|
|
9317
|
+
rec_layout = QGridLayout()
|
|
9318
|
+
|
|
9319
|
+
# Branch removal
|
|
9128
9320
|
self.branch_removal = QLineEdit("0")
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
self.comp_dil = QLineEdit("0")
|
|
9135
|
-
layout.addRow("Voxel distance to merge nearby nodes (Int - compensates for multi-branch identification along thick branch regions):", self.comp_dil)
|
|
9136
|
-
|
|
9137
|
-
self.fast_dil = QPushButton("Fast-Dil")
|
|
9138
|
-
self.fast_dil.setCheckable(True)
|
|
9139
|
-
self.fast_dil.setChecked(True)
|
|
9140
|
-
layout.addRow("(If using above) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fast_dil)
|
|
9141
|
-
|
|
9142
|
-
# auto checkbox (default True)
|
|
9321
|
+
rec_layout.addWidget(QLabel("Skeleton Voxel Branch Length to Remove (Compensates for spines):"), 0, 0)
|
|
9322
|
+
rec_layout.addWidget(self.branch_removal, 0, 1)
|
|
9323
|
+
|
|
9324
|
+
# Auto checkbox
|
|
9143
9325
|
self.auto = QPushButton("Auto")
|
|
9144
9326
|
self.auto.setCheckable(True)
|
|
9145
9327
|
self.auto.setChecked(True)
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9328
|
+
rec_layout.addWidget(QLabel("Attempt to Auto Correct Skeleton Looping:"), 1, 0)
|
|
9329
|
+
rec_layout.addWidget(self.auto, 1, 1)
|
|
9330
|
+
|
|
9331
|
+
rec_group.setLayout(rec_layout)
|
|
9332
|
+
main_layout.addWidget(rec_group)
|
|
9333
|
+
|
|
9334
|
+
# --- Optional Corrections Group ---
|
|
9335
|
+
opt_group = QGroupBox("Optional Corrections")
|
|
9336
|
+
opt_layout = QGridLayout()
|
|
9337
|
+
|
|
9338
|
+
# Max volume
|
|
9339
|
+
self.max_vol = QLineEdit("0")
|
|
9340
|
+
opt_layout.addWidget(QLabel("Maximum Voxel Volume to Retain (Compensates for skeleton looping):"), 0, 0)
|
|
9341
|
+
opt_layout.addWidget(self.max_vol, 0, 1)
|
|
9342
|
+
|
|
9343
|
+
# Component dilation
|
|
9344
|
+
self.comp_dil = QLineEdit("0")
|
|
9345
|
+
opt_layout.addWidget(QLabel("Voxel distance to merge nearby nodes (Compensates for multi-branch regions):"), 1, 0)
|
|
9346
|
+
opt_layout.addWidget(self.comp_dil, 1, 1)
|
|
9347
|
+
|
|
9348
|
+
opt_group.setLayout(opt_layout)
|
|
9349
|
+
main_layout.addWidget(opt_group)
|
|
9350
|
+
|
|
9351
|
+
# Set retain variable but don't add to layout
|
|
9150
9352
|
if not called:
|
|
9151
9353
|
self.retain = QPushButton("Retain")
|
|
9152
9354
|
self.retain.setCheckable(True)
|
|
9153
9355
|
self.retain.setChecked(True)
|
|
9154
|
-
#layout.addRow("Retain Original Edges? (Will be moved to overlay 2):", self.retain)
|
|
9155
9356
|
else:
|
|
9156
9357
|
self.retain = False
|
|
9157
|
-
|
|
9358
|
+
|
|
9158
9359
|
# Add Run button
|
|
9159
9360
|
run_button = QPushButton("Run Node Generation")
|
|
9160
9361
|
run_button.clicked.connect(self.run_gennodes)
|
|
9161
|
-
|
|
9362
|
+
main_layout.addWidget(run_button)
|
|
9162
9363
|
|
|
9163
9364
|
def run_gennodes(self):
|
|
9164
9365
|
|
|
@@ -9279,49 +9480,84 @@ class BranchDialog(QDialog):
|
|
|
9279
9480
|
super().__init__(parent)
|
|
9280
9481
|
self.setWindowTitle("Label Branches (of edges)")
|
|
9281
9482
|
self.setModal(True)
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
# GPU checkbox (default False)
|
|
9292
|
-
self.GPU = QPushButton("GPU")
|
|
9293
|
-
self.GPU.setCheckable(True)
|
|
9294
|
-
self.GPU.setChecked(False)
|
|
9295
|
-
layout.addRow("Use GPU (Note this may need to temporarily downsample your large images which may simplify outputs - Only memory errors but not permission errors for accessing VRAM are handled by default - CPU will never try to downsample):", self.GPU)
|
|
9296
|
-
|
|
9297
|
-
# Branch Fix checkbox (default False)
|
|
9483
|
+
|
|
9484
|
+
# Main layout
|
|
9485
|
+
main_layout = QVBoxLayout(self)
|
|
9486
|
+
|
|
9487
|
+
# --- Correction Options Group ---
|
|
9488
|
+
correction_group = QGroupBox("Correction Options")
|
|
9489
|
+
correction_layout = QGridLayout()
|
|
9490
|
+
|
|
9491
|
+
# Branch Fix checkbox
|
|
9298
9492
|
self.fix = QPushButton("Auto-Correct Branches")
|
|
9299
9493
|
self.fix.setCheckable(True)
|
|
9300
9494
|
self.fix.setChecked(False)
|
|
9301
|
-
|
|
9302
|
-
|
|
9495
|
+
correction_layout.addWidget(QLabel("Attempt to auto-correct branch labels:"), 0, 0)
|
|
9496
|
+
correction_layout.addWidget(self.fix, 0, 1)
|
|
9497
|
+
|
|
9498
|
+
# Fix value
|
|
9303
9499
|
self.fix_val = QLineEdit('4')
|
|
9304
|
-
|
|
9305
|
-
|
|
9500
|
+
correction_layout.addWidget(QLabel("Avg Degree of Nearby Branch Communities to Merge (4-6 recommended):"), 1, 0)
|
|
9501
|
+
correction_layout.addWidget(self.fix_val, 1, 1)
|
|
9502
|
+
|
|
9503
|
+
# Seed
|
|
9504
|
+
self.seed = QLineEdit('')
|
|
9505
|
+
correction_layout.addWidget(QLabel("Random seed for auto correction (int - optional):"), 2, 0)
|
|
9506
|
+
correction_layout.addWidget(self.seed, 2, 1)
|
|
9507
|
+
|
|
9508
|
+
correction_group.setLayout(correction_layout)
|
|
9509
|
+
main_layout.addWidget(correction_group)
|
|
9510
|
+
|
|
9511
|
+
# --- Processing Options Group ---
|
|
9512
|
+
processing_group = QGroupBox("Processing Options")
|
|
9513
|
+
processing_layout = QGridLayout()
|
|
9514
|
+
|
|
9515
|
+
# Downsample factor
|
|
9306
9516
|
self.down_factor = QLineEdit("0")
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9517
|
+
processing_layout.addWidget(QLabel("Internal downsample factor (will recompute nodes):"), 0, 0)
|
|
9518
|
+
processing_layout.addWidget(self.down_factor, 0, 1)
|
|
9519
|
+
|
|
9520
|
+
# Cubic checkbox
|
|
9310
9521
|
self.cubic = QPushButton("Cubic Downsample")
|
|
9311
9522
|
self.cubic.setCheckable(True)
|
|
9312
9523
|
self.cubic.setChecked(False)
|
|
9313
|
-
|
|
9314
|
-
|
|
9524
|
+
processing_layout.addWidget(QLabel("Use cubic downsample? (Slower but preserves structure better):"), 1, 0)
|
|
9525
|
+
processing_layout.addWidget(self.cubic, 1, 1)
|
|
9526
|
+
|
|
9527
|
+
processing_group.setLayout(processing_layout)
|
|
9528
|
+
main_layout.addWidget(processing_group)
|
|
9529
|
+
|
|
9530
|
+
# --- Misc Options Group ---
|
|
9531
|
+
misc_group = QGroupBox("Misc Options")
|
|
9532
|
+
misc_layout = QGridLayout()
|
|
9533
|
+
|
|
9534
|
+
# Nodes checkbox
|
|
9535
|
+
self.nodes = QPushButton("Generate Nodes")
|
|
9536
|
+
self.nodes.setCheckable(True)
|
|
9537
|
+
self.nodes.setChecked(True)
|
|
9538
|
+
misc_layout.addWidget(QLabel("Generate nodes from edges? (Skip if already completed):"), 0, 0)
|
|
9539
|
+
misc_layout.addWidget(self.nodes, 0, 1)
|
|
9540
|
+
|
|
9541
|
+
# GPU checkbox
|
|
9542
|
+
self.GPU = QPushButton("GPU")
|
|
9543
|
+
self.GPU.setCheckable(True)
|
|
9544
|
+
self.GPU.setChecked(False)
|
|
9545
|
+
misc_layout.addWidget(QLabel("Use GPU (May downsample large images):"), 1, 0)
|
|
9546
|
+
misc_layout.addWidget(self.GPU, 1, 1)
|
|
9547
|
+
|
|
9548
|
+
misc_group.setLayout(misc_layout)
|
|
9549
|
+
main_layout.addWidget(misc_group)
|
|
9550
|
+
|
|
9315
9551
|
# Add Run button
|
|
9316
9552
|
run_button = QPushButton("Run Branch Label")
|
|
9317
9553
|
run_button.clicked.connect(self.branch_label)
|
|
9318
|
-
|
|
9554
|
+
main_layout.addWidget(run_button)
|
|
9319
9555
|
|
|
9320
|
-
if self.parent().channel_data[0] is not None:
|
|
9556
|
+
if self.parent().channel_data[0] is not None or self.parent().channel_data[3] is not None:
|
|
9321
9557
|
QMessageBox.critical(
|
|
9322
9558
|
self,
|
|
9323
9559
|
"Alert",
|
|
9324
|
-
"The nodes
|
|
9560
|
+
"The nodes and overlay 2 channels will be intermittently overwritten when running this method"
|
|
9325
9561
|
)
|
|
9326
9562
|
|
|
9327
9563
|
def branch_label(self):
|
|
@@ -9338,8 +9574,7 @@ class BranchDialog(QDialog):
|
|
|
9338
9574
|
cubic = self.cubic.isChecked()
|
|
9339
9575
|
fix = self.fix.isChecked()
|
|
9340
9576
|
fix_val = float(self.fix_val.text()) if self.fix_val.text() else None
|
|
9341
|
-
|
|
9342
|
-
|
|
9577
|
+
seed = int(self.seed.text()) if self.seed.text() else None
|
|
9343
9578
|
|
|
9344
9579
|
original_shape = my_network.edges.shape
|
|
9345
9580
|
original_array = copy.deepcopy(my_network.edges)
|
|
@@ -9360,7 +9595,7 @@ class BranchDialog(QDialog):
|
|
|
9360
9595
|
|
|
9361
9596
|
temp_network.morph_proximity(search = [3,3], fastdil = True) #Detect network of nearby branches
|
|
9362
9597
|
|
|
9363
|
-
temp_network.community_partition(weighted = False, style = 1, dostats = False) #Find communities with louvain, unweighted params
|
|
9598
|
+
temp_network.community_partition(weighted = False, style = 1, dostats = False, seed = seed) #Find communities with louvain, unweighted params
|
|
9364
9599
|
|
|
9365
9600
|
targs = n3d.fix_branches(temp_network.nodes, temp_network.network, temp_network.communities, fix_val)
|
|
9366
9601
|
|