nettracer3d 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nettracer3d might be problematic. Click here for more details.
- nettracer3d/nettracer.py +100 -49
- nettracer3d/nettracer_gui.py +171 -79
- {nettracer3d-1.0.2.dist-info → nettracer3d-1.0.4.dist-info}/METADATA +4 -4
- {nettracer3d-1.0.2.dist-info → nettracer3d-1.0.4.dist-info}/RECORD +8 -8
- {nettracer3d-1.0.2.dist-info → nettracer3d-1.0.4.dist-info}/WHEEL +0 -0
- {nettracer3d-1.0.2.dist-info → nettracer3d-1.0.4.dist-info}/entry_points.txt +0 -0
- {nettracer3d-1.0.2.dist-info → nettracer3d-1.0.4.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-1.0.2.dist-info → nettracer3d-1.0.4.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer.py
CHANGED
|
@@ -1903,6 +1903,7 @@ def mirror_points_for_edge_correction(points_array, bounds, max_r, dim=3):
|
|
|
1903
1903
|
all_points = np.vstack([all_points, mirrored_points])
|
|
1904
1904
|
|
|
1905
1905
|
return all_points
|
|
1906
|
+
|
|
1906
1907
|
def get_max_r_from_proportion(bounds, proportion):
|
|
1907
1908
|
"""
|
|
1908
1909
|
Calculate max_r based on bounds and proportion, matching your generate_r_values logic.
|
|
@@ -3933,7 +3934,7 @@ class Network_3D:
|
|
|
3933
3934
|
"""
|
|
3934
3935
|
self._nodes, num_nodes = label_objects(nodes, structure_3d)
|
|
3935
3936
|
|
|
3936
|
-
def combine_nodes(self, root_nodes, other_nodes, other_ID, identity_dict, root_ID = None, centroids = False):
|
|
3937
|
+
def combine_nodes(self, root_nodes, other_nodes, other_ID, identity_dict, root_ID = None, centroids = False, down_factor = None):
|
|
3937
3938
|
|
|
3938
3939
|
"""Internal method to merge two labelled node arrays into one"""
|
|
3939
3940
|
|
|
@@ -3944,7 +3945,10 @@ class Network_3D:
|
|
|
3944
3945
|
max_val = np.max(root_nodes)
|
|
3945
3946
|
other_nodes[:] = np.where(mask, other_nodes + max_val, 0)
|
|
3946
3947
|
if centroids:
|
|
3947
|
-
new_dict = network_analysis._find_centroids(other_nodes)
|
|
3948
|
+
new_dict = network_analysis._find_centroids(other_nodes, down_factor = down_factor)
|
|
3949
|
+
if down_factor is not None:
|
|
3950
|
+
for item in new_dict:
|
|
3951
|
+
new_dict[item] = down_factor * new_dict[item]
|
|
3948
3952
|
self.node_centroids.update(new_dict)
|
|
3949
3953
|
|
|
3950
3954
|
if root_ID is not None:
|
|
@@ -3984,7 +3988,7 @@ class Network_3D:
|
|
|
3984
3988
|
|
|
3985
3989
|
return nodes, identity_dict
|
|
3986
3990
|
|
|
3987
|
-
def merge_nodes(self, addn_nodes_name, label_nodes = True, root_id = "Root_Nodes", centroids = False):
|
|
3991
|
+
def merge_nodes(self, addn_nodes_name, label_nodes = True, root_id = "Root_Nodes", centroids = False, down_factor = None):
|
|
3988
3992
|
"""
|
|
3989
3993
|
Merges the self._nodes attribute with alternate labelled node images. The alternate nodes can be inputted as a string for a filepath to a tif,
|
|
3990
3994
|
or as a directory address containing only tif images, which will merge the _nodes attribute with all tifs in the folder. The _node_identities attribute
|
|
@@ -4005,19 +4009,21 @@ class Network_3D:
|
|
|
4005
4009
|
identity_dict = {} #A dictionary to deliniate the node identities
|
|
4006
4010
|
|
|
4007
4011
|
if centroids:
|
|
4008
|
-
self.node_centroids = network_analysis._find_centroids(self._nodes)
|
|
4009
|
-
|
|
4012
|
+
self.node_centroids = network_analysis._find_centroids(self._nodes, down_factor = down_factor)
|
|
4013
|
+
if down_factor is not None:
|
|
4014
|
+
for item in self.node_centroids:
|
|
4015
|
+
self.node_centroids[item] = down_factor * self.node_centroids[item]
|
|
4010
4016
|
|
|
4011
4017
|
try: #Try presumes the input is a tif
|
|
4012
4018
|
addn_nodes = tifffile.imread(addn_nodes_name) #If not this will fail and activate the except block
|
|
4013
4019
|
|
|
4014
4020
|
if label_nodes is True:
|
|
4015
4021
|
addn_nodes, num_nodes2 = label_objects(addn_nodes) # Label the node objects. Note this presumes no overlap between node masks.
|
|
4016
|
-
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids) #This method stacks labelled arrays
|
|
4022
|
+
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids, down_factor = down_factor) #This method stacks labelled arrays
|
|
4017
4023
|
num_nodes = np.max(node_labels)
|
|
4018
4024
|
|
|
4019
4025
|
else: #If nodes already labelled
|
|
4020
|
-
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids)
|
|
4026
|
+
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids, down_factor = down_factor)
|
|
4021
4027
|
num_nodes = int(np.max(node_labels))
|
|
4022
4028
|
|
|
4023
4029
|
except: #Exception presumes the input is a directory containing multiple tifs, to allow multi-node stackage.
|
|
@@ -4035,15 +4041,15 @@ class Network_3D:
|
|
|
4035
4041
|
if label_nodes is True:
|
|
4036
4042
|
addn_nodes, num_nodes2 = label_objects(addn_nodes) # Label the node objects. Note this presumes no overlap between node masks.
|
|
4037
4043
|
if i == 0:
|
|
4038
|
-
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids)
|
|
4044
|
+
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids, down_factor = down_factor)
|
|
4039
4045
|
else:
|
|
4040
|
-
node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids)
|
|
4046
|
+
node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids, down_factor = down_factor)
|
|
4041
4047
|
|
|
4042
4048
|
else:
|
|
4043
4049
|
if i == 0:
|
|
4044
|
-
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids)
|
|
4050
|
+
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids, down_factor = down_factor)
|
|
4045
4051
|
else:
|
|
4046
|
-
node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids)
|
|
4052
|
+
node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids, down_factor = down_factor)
|
|
4047
4053
|
except Exception as e:
|
|
4048
4054
|
print("Could not open additional nodes, verify they are being inputted correctly...")
|
|
4049
4055
|
|
|
@@ -6049,57 +6055,94 @@ class Network_3D:
|
|
|
6049
6055
|
self.node_identities = modify_dict
|
|
6050
6056
|
|
|
6051
6057
|
|
|
6052
|
-
def nearest_neighbors_avg(self, root, targ, xy_scale = 1, z_scale = 1, num = 1, heatmap = False, threed = True, numpy = False, quant = False, centroids = True):
|
|
6053
|
-
|
|
6054
|
-
def distribute_points_uniformly(n, shape, z_scale, xy_scale, num, is_2d=False):
|
|
6058
|
+
def nearest_neighbors_avg(self, root, targ, xy_scale = 1, z_scale = 1, num = 1, heatmap = False, threed = True, numpy = False, quant = False, centroids = True, mask = None):
|
|
6055
6059
|
|
|
6060
|
+
def distribute_points_uniformly(n, shape, z_scale, xy_scale, num, is_2d=False, mask=None):
|
|
6056
6061
|
from scipy.spatial import KDTree
|
|
6057
|
-
|
|
6058
6062
|
if n <= 1:
|
|
6059
6063
|
return 0
|
|
6060
|
-
|
|
6061
|
-
# Calculate total positions and sampling step
|
|
6062
|
-
total_positions = np.prod(shape)
|
|
6063
|
-
if n >= total_positions:
|
|
6064
|
-
# If we want more points than positions, just return scaled unit distance
|
|
6065
|
-
return xy_scale if is_2d else min(z_scale, xy_scale)
|
|
6066
6064
|
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6065
|
+
if mask is not None:
|
|
6066
|
+
# Handle mask-based distribution
|
|
6067
|
+
# Find all valid positions where mask is True
|
|
6068
|
+
valid_positions = np.where(mask)
|
|
6069
|
+
total_valid_positions = len(valid_positions[0])
|
|
6070
|
+
|
|
6071
|
+
if total_valid_positions == 0:
|
|
6072
|
+
raise ValueError("No valid positions found in mask")
|
|
6073
|
+
|
|
6074
|
+
if n >= total_valid_positions:
|
|
6075
|
+
# If we want more points than valid positions, return scaled unit distance
|
|
6076
|
+
return xy_scale if is_2d else min(z_scale, xy_scale)
|
|
6077
|
+
|
|
6078
|
+
# Create uniformly spaced indices within valid positions
|
|
6079
|
+
valid_indices = np.linspace(0, total_valid_positions - 1, n, dtype=int)
|
|
6080
|
+
|
|
6081
|
+
# Convert to coordinates and apply scaling
|
|
6082
|
+
coords = []
|
|
6083
|
+
for idx in valid_indices:
|
|
6084
|
+
if len(shape) == 3:
|
|
6085
|
+
coord = (valid_positions[0][idx], valid_positions[1][idx], valid_positions[2][idx])
|
|
6086
|
+
scaled_coord = [coord[0] * z_scale, coord[1] * xy_scale, coord[2] * xy_scale]
|
|
6087
|
+
elif len(shape) == 2:
|
|
6088
|
+
coord = (valid_positions[0][idx], valid_positions[1][idx])
|
|
6089
|
+
scaled_coord = [coord[0] * xy_scale, coord[1] * xy_scale]
|
|
6090
|
+
coords.append(scaled_coord)
|
|
6091
|
+
|
|
6092
|
+
coords = np.array(coords)
|
|
6093
|
+
|
|
6094
|
+
# Find a good query point (closest to center of valid region)
|
|
6074
6095
|
if len(shape) == 3:
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6096
|
+
center_pos = [np.mean(valid_positions[0]) * z_scale,
|
|
6097
|
+
np.mean(valid_positions[1]) * xy_scale,
|
|
6098
|
+
np.mean(valid_positions[2]) * xy_scale]
|
|
6099
|
+
else:
|
|
6100
|
+
center_pos = [np.mean(valid_positions[0]) * xy_scale,
|
|
6101
|
+
np.mean(valid_positions[1]) * xy_scale]
|
|
6102
|
+
|
|
6103
|
+
# Find point closest to center of valid region
|
|
6104
|
+
center_distances = np.sum((coords - center_pos)**2, axis=1)
|
|
6105
|
+
middle_idx = np.argmin(center_distances)
|
|
6106
|
+
query_point = coords[middle_idx]
|
|
6107
|
+
|
|
6108
|
+
else:
|
|
6109
|
+
# Original behavior when no mask is provided
|
|
6110
|
+
total_positions = np.prod(shape)
|
|
6111
|
+
if n >= total_positions:
|
|
6112
|
+
return xy_scale if is_2d else min(z_scale, xy_scale)
|
|
6113
|
+
|
|
6114
|
+
# Create uniformly spaced indices
|
|
6115
|
+
indices = np.linspace(0, total_positions - 1, n, dtype=int)
|
|
6116
|
+
|
|
6117
|
+
# Convert flat indices to coordinates
|
|
6118
|
+
coords = []
|
|
6119
|
+
for idx in indices:
|
|
6120
|
+
coord = np.unravel_index(idx, shape)
|
|
6121
|
+
if len(shape) == 3:
|
|
6122
|
+
scaled_coord = [coord[0] * z_scale, coord[1] * xy_scale, coord[2] * xy_scale]
|
|
6123
|
+
elif len(shape) == 2:
|
|
6124
|
+
scaled_coord = [coord[0] * xy_scale, coord[1] * xy_scale]
|
|
6125
|
+
coords.append(scaled_coord)
|
|
6126
|
+
|
|
6127
|
+
coords = np.array(coords)
|
|
6128
|
+
|
|
6129
|
+
# Pick a point near the middle of the array
|
|
6130
|
+
middle_idx = len(coords) // 2
|
|
6131
|
+
query_point = coords[middle_idx]
|
|
6083
6132
|
|
|
6084
6133
|
# Build KDTree
|
|
6085
6134
|
tree = KDTree(coords)
|
|
6086
6135
|
|
|
6087
|
-
# Pick a point near the middle of the array
|
|
6088
|
-
middle_idx = len(coords) // 2
|
|
6089
|
-
query_point = coords[middle_idx]
|
|
6090
|
-
|
|
6091
6136
|
# Find the num+1 nearest neighbors (including the point itself)
|
|
6092
6137
|
distances, indices = tree.query(query_point, k=num+1)
|
|
6093
6138
|
|
|
6094
6139
|
# Exclude the point itself (distance 0) and get the actual neighbors
|
|
6095
6140
|
neighbor_distances = distances[1:num+1]
|
|
6096
|
-
|
|
6097
6141
|
if num == n:
|
|
6098
6142
|
neighbor_distances[-1] = neighbor_distances[-2]
|
|
6099
6143
|
|
|
6100
6144
|
avg_distance = np.mean(neighbor_distances)
|
|
6101
6145
|
|
|
6102
|
-
|
|
6103
6146
|
return avg_distance
|
|
6104
6147
|
|
|
6105
6148
|
do_borders = not centroids
|
|
@@ -6162,11 +6205,11 @@ class Network_3D:
|
|
|
6162
6205
|
if heatmap:
|
|
6163
6206
|
root_set = []
|
|
6164
6207
|
compare_set = []
|
|
6165
|
-
if root is None:
|
|
6166
|
-
|
|
6167
|
-
root_set = list(self.node_centroids.keys())
|
|
6208
|
+
if root is None and not do_borders:
|
|
6168
6209
|
compare_set = root_set
|
|
6169
|
-
|
|
6210
|
+
if not do_borders:
|
|
6211
|
+
root_set = list(self.node_centroids.keys())
|
|
6212
|
+
elif self.node_identities is not None:
|
|
6170
6213
|
for node, iden in self.node_identities.items():
|
|
6171
6214
|
|
|
6172
6215
|
if iden == root:
|
|
@@ -6194,7 +6237,8 @@ class Network_3D:
|
|
|
6194
6237
|
targ = [targ]
|
|
6195
6238
|
|
|
6196
6239
|
compare_set_neigh = approx_boundaries(self.nodes, targ, self.node_identities, keep_labels = False)
|
|
6197
|
-
|
|
6240
|
+
|
|
6241
|
+
avg, output = proximity.average_nearest_neighbor_distances(self.node_centroids, root_set_neigh, compare_set_neigh, xy_scale=self.xy_scale, z_scale=self.z_scale, num = num, do_borders = do_borders)
|
|
6198
6242
|
|
|
6199
6243
|
if quant:
|
|
6200
6244
|
try:
|
|
@@ -6236,7 +6280,15 @@ class Network_3D:
|
|
|
6236
6280
|
else:
|
|
6237
6281
|
is_2d = False
|
|
6238
6282
|
|
|
6239
|
-
|
|
6283
|
+
if root_set == []:
|
|
6284
|
+
avail_nodes = np.unique(self.nodes)
|
|
6285
|
+
compare_set = list(avail_nodes)
|
|
6286
|
+
if 0 in compare_set:
|
|
6287
|
+
del compare_set[0]
|
|
6288
|
+
root_set = compare_set
|
|
6289
|
+
elif compare_set == []:
|
|
6290
|
+
compare_set = root_set
|
|
6291
|
+
pred = distribute_points_uniformly(len(compare_set), bounds, self.z_scale, self.xy_scale, num = num, is_2d = is_2d, mask = mask)
|
|
6240
6292
|
|
|
6241
6293
|
node_intensity = {}
|
|
6242
6294
|
import math
|
|
@@ -6244,7 +6296,6 @@ class Network_3D:
|
|
|
6244
6296
|
|
|
6245
6297
|
for node in root_set:
|
|
6246
6298
|
node_intensity[node] = math.log(pred/output[node])
|
|
6247
|
-
#print(output[node])
|
|
6248
6299
|
node_centroids[node] = self.node_centroids[node]
|
|
6249
6300
|
|
|
6250
6301
|
if numpy:
|
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -4,7 +4,7 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QG
|
|
|
4
4
|
QHBoxLayout, QSlider, QMenuBar, QMenu, QDialog,
|
|
5
5
|
QFormLayout, QLineEdit, QPushButton, QFileDialog,
|
|
6
6
|
QLabel, QComboBox, QMessageBox, QTableView, QInputDialog,
|
|
7
|
-
QMenu, QTabWidget, QGroupBox)
|
|
7
|
+
QMenu, QTabWidget, QGroupBox, QCheckBox)
|
|
8
8
|
from PyQt6.QtCore import (QPoint, Qt, QAbstractTableModel, QTimer, QThread, pyqtSignal, QObject, QCoreApplication, QEvent, QEventLoop)
|
|
9
9
|
import numpy as np
|
|
10
10
|
import time
|
|
@@ -1280,7 +1280,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1280
1280
|
|
|
1281
1281
|
if my_network.node_identities is not None:
|
|
1282
1282
|
identity_menu = QMenu("Show Identity", self)
|
|
1283
|
-
|
|
1283
|
+
idens = list(set(my_network.node_identities.values()))
|
|
1284
|
+
idens.sort()
|
|
1285
|
+
for item in idens:
|
|
1284
1286
|
show_identity = identity_menu.addAction(f"ID: {item}")
|
|
1285
1287
|
show_identity.triggered.connect(lambda checked, item=item: self.handle_show_identities(sort=item))
|
|
1286
1288
|
context_menu.addMenu(identity_menu)
|
|
@@ -5471,7 +5473,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5471
5473
|
|
|
5472
5474
|
elif sort == 'Merge Nodes':
|
|
5473
5475
|
try:
|
|
5474
|
-
|
|
5475
5476
|
if my_network.nodes is None:
|
|
5476
5477
|
QMessageBox.critical(
|
|
5477
5478
|
self,
|
|
@@ -5479,72 +5480,118 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5479
5480
|
"Please load your first set of nodes into the 'Nodes' channel first"
|
|
5480
5481
|
)
|
|
5481
5482
|
return
|
|
5482
|
-
|
|
5483
5483
|
if len(np.unique(my_network.nodes)) < 3:
|
|
5484
5484
|
self.show_label_dialog()
|
|
5485
|
-
|
|
5486
|
-
#
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5485
|
+
|
|
5486
|
+
# Create custom dialog
|
|
5487
|
+
dialog = QDialog(self)
|
|
5488
|
+
dialog.setWindowTitle("Merge Nodes Configuration")
|
|
5489
|
+
dialog.setModal(True)
|
|
5490
|
+
dialog.resize(400, 200)
|
|
5491
|
+
|
|
5492
|
+
layout = QVBoxLayout(dialog)
|
|
5493
|
+
|
|
5494
|
+
# Selection type
|
|
5495
|
+
type_layout = QHBoxLayout()
|
|
5496
|
+
type_label = QLabel("Selection Type:")
|
|
5497
|
+
type_combo = QComboBox()
|
|
5498
|
+
type_combo.addItems(["TIFF File", "Directory"])
|
|
5499
|
+
type_layout.addWidget(type_label)
|
|
5500
|
+
type_layout.addWidget(type_combo)
|
|
5501
|
+
layout.addLayout(type_layout)
|
|
5502
|
+
|
|
5503
|
+
# Centroids checkbox
|
|
5504
|
+
centroids_layout = QHBoxLayout()
|
|
5505
|
+
centroids_check = QCheckBox("Compute node centroids for each image prior to merging")
|
|
5506
|
+
centroids_layout.addWidget(centroids_check)
|
|
5507
|
+
layout.addLayout(centroids_layout)
|
|
5508
|
+
|
|
5509
|
+
# Down factor for centroid calculation
|
|
5510
|
+
down_factor_layout = QHBoxLayout()
|
|
5511
|
+
down_factor_label = QLabel("Down Factor (for centroid calculation downsampling):")
|
|
5512
|
+
down_factor_edit = QLineEdit()
|
|
5513
|
+
down_factor_edit.setText("1") # Default value
|
|
5514
|
+
down_factor_edit.setPlaceholderText("Enter down factor (e.g., 1, 2, 4)")
|
|
5515
|
+
down_factor_layout.addWidget(down_factor_label)
|
|
5516
|
+
down_factor_layout.addWidget(down_factor_edit)
|
|
5517
|
+
layout.addLayout(down_factor_layout)
|
|
5518
|
+
|
|
5519
|
+
# Buttons
|
|
5520
|
+
button_layout = QHBoxLayout()
|
|
5521
|
+
accept_button = QPushButton("Accept")
|
|
5522
|
+
cancel_button = QPushButton("Cancel")
|
|
5523
|
+
button_layout.addWidget(accept_button)
|
|
5524
|
+
button_layout.addWidget(cancel_button)
|
|
5525
|
+
layout.addLayout(button_layout)
|
|
5526
|
+
|
|
5527
|
+
# Connect buttons
|
|
5528
|
+
accept_button.clicked.connect(dialog.accept)
|
|
5529
|
+
cancel_button.clicked.connect(dialog.reject)
|
|
5530
|
+
|
|
5531
|
+
# Execute dialog
|
|
5532
|
+
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
5533
|
+
# Get values from dialog
|
|
5534
|
+
selection_type = type_combo.currentText()
|
|
5535
|
+
centroids = centroids_check.isChecked()
|
|
5536
|
+
|
|
5537
|
+
# Validate and get down_factor
|
|
5538
5538
|
try:
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5539
|
+
down_factor = int(down_factor_edit.text())
|
|
5540
|
+
if down_factor <= 0:
|
|
5541
|
+
raise ValueError("Down factor must be positive")
|
|
5542
|
+
except ValueError as e:
|
|
5543
|
+
QMessageBox.critical(
|
|
5544
|
+
self,
|
|
5545
|
+
"Invalid Input",
|
|
5546
|
+
f"Invalid down factor: {str(e)}"
|
|
5547
|
+
)
|
|
5548
|
+
return
|
|
5549
|
+
|
|
5550
|
+
# Handle file/directory selection based on combo box choice
|
|
5551
|
+
if selection_type == "TIFF File":
|
|
5552
|
+
filename, _ = QFileDialog.getOpenFileName(
|
|
5553
|
+
self,
|
|
5554
|
+
"Select TIFF file",
|
|
5555
|
+
"",
|
|
5556
|
+
"TIFF files (*.tiff *.tif)"
|
|
5557
|
+
)
|
|
5558
|
+
if filename:
|
|
5559
|
+
selected_path = filename
|
|
5560
|
+
else:
|
|
5561
|
+
return # User cancelled file selection
|
|
5562
|
+
else: # Directory
|
|
5563
|
+
file_dialog = QFileDialog(self)
|
|
5564
|
+
file_dialog.setOption(QFileDialog.Option.DontUseNativeDialog)
|
|
5565
|
+
file_dialog.setOption(QFileDialog.Option.ReadOnly)
|
|
5566
|
+
file_dialog.setFileMode(QFileDialog.FileMode.Directory)
|
|
5567
|
+
file_dialog.setViewMode(QFileDialog.ViewMode.Detail)
|
|
5568
|
+
if file_dialog.exec() == QFileDialog.DialogCode.Accepted:
|
|
5569
|
+
selected_path = file_dialog.directory().absolutePath()
|
|
5570
|
+
else:
|
|
5571
|
+
return # User cancelled directory selection
|
|
5572
|
+
|
|
5573
|
+
if down_factor == 1:
|
|
5574
|
+
down_factor = None
|
|
5575
|
+
# Call merge_nodes with all parameters
|
|
5576
|
+
my_network.merge_nodes(
|
|
5577
|
+
selected_path,
|
|
5578
|
+
root_id=self.node_name,
|
|
5579
|
+
centroids=centroids,
|
|
5580
|
+
down_factor=down_factor
|
|
5581
|
+
)
|
|
5582
|
+
|
|
5583
|
+
self.load_channel(0, my_network.nodes, True)
|
|
5584
|
+
|
|
5585
|
+
if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
|
|
5586
|
+
try:
|
|
5587
|
+
self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
|
|
5588
|
+
except Exception as e:
|
|
5589
|
+
print(f"Error loading node identity table: {e}")
|
|
5590
|
+
|
|
5591
|
+
if centroids:
|
|
5592
|
+
self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
5593
|
+
|
|
5546
5594
|
except Exception as e:
|
|
5547
|
-
|
|
5548
5595
|
QMessageBox.critical(
|
|
5549
5596
|
self,
|
|
5550
5597
|
"Error Merging",
|
|
@@ -5567,7 +5614,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5567
5614
|
)
|
|
5568
5615
|
|
|
5569
5616
|
self.last_load = directory
|
|
5570
|
-
|
|
5617
|
+
self.last_saved = os.path.dirname(directory)
|
|
5618
|
+
self.last_save_name = directory
|
|
5571
5619
|
|
|
5572
5620
|
if directory != "":
|
|
5573
5621
|
|
|
@@ -5649,8 +5697,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5649
5697
|
f"Failed to load Network 3D Object: {str(e)}"
|
|
5650
5698
|
)
|
|
5651
5699
|
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
5700
|
def load_network(self):
|
|
5655
5701
|
"""Load in the network from a .xlsx (need to add .csv support)"""
|
|
5656
5702
|
|
|
@@ -8993,7 +9039,7 @@ class ComNeighborDialog(QDialog):
|
|
|
8993
9039
|
|
|
8994
9040
|
mode = self.mode.currentIndex()
|
|
8995
9041
|
|
|
8996
|
-
seed =
|
|
9042
|
+
seed = int(self.seed.text()) if self.seed.text().strip() else 42
|
|
8997
9043
|
|
|
8998
9044
|
limit = int(self.limit.text()) if self.limit.text().strip() else None
|
|
8999
9045
|
|
|
@@ -9190,6 +9236,11 @@ class NearNeighDialog(QDialog):
|
|
|
9190
9236
|
self.numpy.setChecked(False)
|
|
9191
9237
|
self.numpy.clicked.connect(self.toggle_map)
|
|
9192
9238
|
heatmap_layout.addRow("Overlay:", self.numpy)
|
|
9239
|
+
|
|
9240
|
+
self.mode = QComboBox()
|
|
9241
|
+
self.mode.addItems(["Anywhere", "Within Masked Bounds of Edges", "Within Masked Bounds of Overlay1", "Within Masked Bounds of Overlay2"])
|
|
9242
|
+
self.mode.setCurrentIndex(0)
|
|
9243
|
+
heatmap_layout.addRow("For heatmap, measure theoretical point distribution how?", self.mode)
|
|
9193
9244
|
|
|
9194
9245
|
main_layout.addWidget(heatmap_group)
|
|
9195
9246
|
|
|
@@ -9281,35 +9332,48 @@ class NearNeighDialog(QDialog):
|
|
|
9281
9332
|
root = None
|
|
9282
9333
|
targ = None
|
|
9283
9334
|
|
|
9335
|
+
mode = self.mode.currentIndex()
|
|
9336
|
+
|
|
9337
|
+
if mode == 0:
|
|
9338
|
+
mask = None
|
|
9339
|
+
else:
|
|
9340
|
+
try:
|
|
9341
|
+
mask = self.parent().channel_data[mode] != 0
|
|
9342
|
+
except:
|
|
9343
|
+
print("Could not binarize mask")
|
|
9344
|
+
mask = None
|
|
9345
|
+
|
|
9284
9346
|
heatmap = self.map.isChecked()
|
|
9285
9347
|
threed = self.threed.isChecked()
|
|
9286
9348
|
numpy = self.numpy.isChecked()
|
|
9287
9349
|
num = int(self.num.text()) if self.num.text().strip() else 1
|
|
9288
9350
|
quant = self.quant.isChecked()
|
|
9289
9351
|
centroids = self.centroids.isChecked()
|
|
9352
|
+
|
|
9290
9353
|
if not centroids:
|
|
9354
|
+
print("Using 1 nearest neighbor due to not using centroids")
|
|
9291
9355
|
num = 1
|
|
9292
9356
|
|
|
9293
9357
|
if root is not None and targ is not None:
|
|
9294
9358
|
title = f"Nearest {num} Neighbor(s) Distance of {targ} from {root}"
|
|
9295
|
-
header = f"Shortest Distance to Closest {num} {targ}(s)"
|
|
9359
|
+
header = f"Avg Shortest Distance to Closest {num} {targ}(s)"
|
|
9296
9360
|
header2 = f"{root} Node ID"
|
|
9297
9361
|
header3 = f'Theoretical Uniform Distance to Closest {num} {targ}(s)'
|
|
9298
9362
|
else:
|
|
9299
9363
|
title = f"Nearest {num} Neighbor(s) Distance Between Nodes"
|
|
9300
|
-
header = f"Shortest Distance to Closest {num} Nodes"
|
|
9364
|
+
header = f"Avg Shortest Distance to Closest {num} Nodes"
|
|
9301
9365
|
header2 = "Root Node ID"
|
|
9302
9366
|
header3 = f'Simulated Theoretical Uniform Distance to Closest {num} Nodes'
|
|
9303
9367
|
|
|
9304
|
-
if
|
|
9368
|
+
if my_network.node_centroids is None:
|
|
9305
9369
|
self.parent().show_centroid_dialog()
|
|
9306
9370
|
if my_network.node_centroids is None:
|
|
9307
9371
|
return
|
|
9308
9372
|
|
|
9309
9373
|
if not numpy:
|
|
9310
|
-
avg, output, quant_overlay, pred = 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)
|
|
9374
|
+
avg, output, quant_overlay, pred = 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, mask = mask)
|
|
9311
9375
|
else:
|
|
9312
|
-
avg, output, overlay, quant_overlay, pred = 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)
|
|
9376
|
+
avg, output, overlay, quant_overlay, pred = 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, mask = mask)
|
|
9313
9377
|
self.parent().load_channel(3, overlay, data = True, preserve_zoom = (self.parent().ax.get_xlim(), self.parent().ax.get_ylim()))
|
|
9314
9378
|
|
|
9315
9379
|
if quant_overlay is not None:
|
|
@@ -10064,9 +10128,33 @@ class ViolinDialog(QDialog):
|
|
|
10064
10128
|
|
|
10065
10129
|
return df_normalized
|
|
10066
10130
|
|
|
10131
|
+
def show_in_table(self, df, metric, title):
|
|
10132
|
+
|
|
10133
|
+
# Create new table
|
|
10134
|
+
table = CustomTableView(self.parent())
|
|
10135
|
+
table.setModel(PandasModel(df))
|
|
10136
|
+
|
|
10137
|
+
try:
|
|
10138
|
+
first_column_name = table.model()._data.columns[0]
|
|
10139
|
+
table.sort_table(first_column_name, ascending=True)
|
|
10140
|
+
except:
|
|
10141
|
+
pass
|
|
10142
|
+
|
|
10143
|
+
# Add to tabbed widget
|
|
10144
|
+
if title is None:
|
|
10145
|
+
self.parent().tabbed_data.add_table(f"{metric} Analysis", table)
|
|
10146
|
+
else:
|
|
10147
|
+
self.parent().tabbed_data.add_table(f"{title}", table)
|
|
10148
|
+
|
|
10149
|
+
|
|
10150
|
+
|
|
10151
|
+
# Adjust column widths to content
|
|
10152
|
+
for column in range(table.model().columnCount(None)):
|
|
10153
|
+
table.resizeColumnToContents(column)
|
|
10154
|
+
|
|
10067
10155
|
def run(self):
|
|
10068
10156
|
|
|
10069
|
-
def df_to_dict_by_rows(df, row_indices):
|
|
10157
|
+
def df_to_dict_by_rows(df, row_indices, title):
|
|
10070
10158
|
"""
|
|
10071
10159
|
Convert a pandas DataFrame to a dictionary by selecting specific rows.
|
|
10072
10160
|
No normalization - dataframe is already normalized.
|
|
@@ -10095,6 +10183,10 @@ class ViolinDialog(QDialog):
|
|
|
10095
10183
|
for column in masked_df.columns:
|
|
10096
10184
|
result_dict[column] = masked_df[column].tolist()
|
|
10097
10185
|
|
|
10186
|
+
masked_df.insert(0, "NodeIDs", row_indices)
|
|
10187
|
+
self.show_in_table(masked_df, metric = "NodeID", title = title)
|
|
10188
|
+
|
|
10189
|
+
|
|
10098
10190
|
return result_dict
|
|
10099
10191
|
|
|
10100
10192
|
from . import neighborhoods
|
|
@@ -10112,10 +10204,10 @@ class ViolinDialog(QDialog):
|
|
|
10112
10204
|
if iden in parse:
|
|
10113
10205
|
iden_list.append(item)
|
|
10114
10206
|
except:
|
|
10115
|
-
if iden == item:
|
|
10207
|
+
if (iden == my_network.node_identities[item]):
|
|
10116
10208
|
iden_list.append(item)
|
|
10117
10209
|
|
|
10118
|
-
violin_dict = df_to_dict_by_rows(self.df, iden_list)
|
|
10210
|
+
violin_dict = df_to_dict_by_rows(self.df, iden_list, f"Z-Score-like Channel Intensities of Identity {iden}, {len(iden_list)} Nodes")
|
|
10119
10211
|
|
|
10120
10212
|
neighborhoods.create_violin_plots(violin_dict, graph_title=f"Z-Score-like Channel Intensities of Identity {iden}, {len(iden_list)} Nodes")
|
|
10121
10213
|
|
|
@@ -10124,11 +10216,11 @@ class ViolinDialog(QDialog):
|
|
|
10124
10216
|
|
|
10125
10217
|
com = self.coms.currentText()
|
|
10126
10218
|
|
|
10127
|
-
com_dict = n3d.invert_dict(my_network.communities)
|
|
10219
|
+
com_dict = n3d.invert_dict(my_network.communities)
|
|
10128
10220
|
|
|
10129
10221
|
com_list = com_dict[int(com)]
|
|
10130
10222
|
|
|
10131
|
-
violin_dict = df_to_dict_by_rows(self.df, com_list)
|
|
10223
|
+
violin_dict = df_to_dict_by_rows(self.df, com_list, f"Z-Score-like Channel Intensities of Community/Neighborhood {com}, {len(com_list)} Nodes")
|
|
10132
10224
|
|
|
10133
10225
|
neighborhoods.create_violin_plots(violin_dict, graph_title=f"Z-Score-like Channel Intensities of Community/Neighborhood {com}, {len(com_list)} Nodes")
|
|
10134
10226
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nettracer3d
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
|
|
5
5
|
Author-email: Liam McLaughlin <liamm@wustl.edu>
|
|
6
6
|
Project-URL: Documentation, https://nettracer3d.readthedocs.io/en/latest/
|
|
@@ -110,7 +110,7 @@ McLaughlin, L., Zhang, B., Sharma, S. et al. Three dimensional multiscalar neuro
|
|
|
110
110
|
|
|
111
111
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
112
112
|
|
|
113
|
-
-- Version 1.0.
|
|
113
|
+
-- Version 1.0.4 Updates --
|
|
114
114
|
|
|
115
|
-
*
|
|
116
|
-
*
|
|
115
|
+
* Some small bug fixes and adjustments
|
|
116
|
+
* Heatmap theoretical distances can now be calculated based on an area constrained within a binary mask.
|
|
@@ -5,8 +5,8 @@ nettracer3d/excelotron.py,sha256=aNof6k-DgMxVyFgsl3ltSCxG4vZW49cuvCBzfzhYhUY,750
|
|
|
5
5
|
nettracer3d/modularity.py,sha256=pborVcDBvICB2-g8lNoSVZbIReIBlfeBmjFbPYmtq7Y,22443
|
|
6
6
|
nettracer3d/morphology.py,sha256=jyDjYzrZ4LvI5jOyw8DLsxmo-i5lpqHsejYpW7Tq7Mo,19786
|
|
7
7
|
nettracer3d/neighborhoods.py,sha256=lh4_zuTwgTq-u8VHy72buBfZf58FEJo9sH3IIBuZr68,52735
|
|
8
|
-
nettracer3d/nettracer.py,sha256=
|
|
9
|
-
nettracer3d/nettracer_gui.py,sha256=
|
|
8
|
+
nettracer3d/nettracer.py,sha256=6r5aGtmiHxGkfCh5lJuREAoloZ3WKvcaRAD7kmWXedk,273957
|
|
9
|
+
nettracer3d/nettracer_gui.py,sha256=0QzoDa6o2z-iVZzM3v4tePFV-tvw-lkD_24tQ0fZ1sU,666134
|
|
10
10
|
nettracer3d/network_analysis.py,sha256=kBzsVaq4dZkMe0k-VGvQIUvM-tK0ZZ8bvb-wtsugZRQ,46150
|
|
11
11
|
nettracer3d/network_draw.py,sha256=F7fw6Pcf4qWOhdKwLmhwqWdschbDlHzwCVolQC9imeU,14117
|
|
12
12
|
nettracer3d/node_draw.py,sha256=kZcR1PekLg0riioNeGcALIXQyZ5PtHA_9MT6z7Zovdk,10401
|
|
@@ -17,9 +17,9 @@ nettracer3d/segmenter.py,sha256=aOO3PwZ2UeizEIFX7N8midwzTzbEDzgM2jQ7WTdbrUg,7067
|
|
|
17
17
|
nettracer3d/segmenter_GPU.py,sha256=OUekQljLKPiC4d4hNZmqrRa9HSVQ6HcCnILiAfHE5Hg,78051
|
|
18
18
|
nettracer3d/simple_network.py,sha256=dkG4jpc4zzdeuoaQobgGfL3PNo6N8dGKQ5hEEubFIvA,9947
|
|
19
19
|
nettracer3d/smart_dilate.py,sha256=TvRUh6B4q4zIdCO1BWH-xgTdND5OUNmo99eyxG9oIAU,27145
|
|
20
|
-
nettracer3d-1.0.
|
|
21
|
-
nettracer3d-1.0.
|
|
22
|
-
nettracer3d-1.0.
|
|
23
|
-
nettracer3d-1.0.
|
|
24
|
-
nettracer3d-1.0.
|
|
25
|
-
nettracer3d-1.0.
|
|
20
|
+
nettracer3d-1.0.4.dist-info/licenses/LICENSE,sha256=jnNT-yBeIAKAHpYthPvLeqCzJ6nSurgnKmloVnfsjCI,764
|
|
21
|
+
nettracer3d-1.0.4.dist-info/METADATA,sha256=VMs_Lcaa3xnnLTrror42GmRUJBdzIcyGk0sMtJZZ9Eo,7120
|
|
22
|
+
nettracer3d-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
nettracer3d-1.0.4.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
|
|
24
|
+
nettracer3d-1.0.4.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
|
|
25
|
+
nettracer3d-1.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|