nettracer3d 0.7.7__py3-none-any.whl → 0.7.9__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/excelotron.py +51 -1
- nettracer3d/neighborhoods.py +131 -0
- nettracer3d/nettracer.py +88 -2
- nettracer3d/nettracer_gui.py +484 -127
- nettracer3d/proximity.py +1 -1
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.9.dist-info}/METADATA +3 -3
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.9.dist-info}/RECORD +11 -11
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.9.dist-info}/WHEEL +0 -0
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.9.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.9.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.9.dist-info}/top_level.txt +0 -0
nettracer3d/excelotron.py
CHANGED
|
@@ -1599,7 +1599,13 @@ class ExcelToDictGUI(QMainWindow):
|
|
|
1599
1599
|
remapped_data = self.identity_remap_widget.get_remapped_identities(filtered_data)
|
|
1600
1600
|
result_dict[key_name] = remapped_data
|
|
1601
1601
|
elif key_name == 'Numerical IDs':
|
|
1602
|
-
|
|
1602
|
+
|
|
1603
|
+
# Check if user actually dropped a numerical IDs column
|
|
1604
|
+
if widget_id not in self.dict_columns or 'data' not in self.dict_columns[widget_id]:
|
|
1605
|
+
# Auto-generate sequential IDs and assign to column_data
|
|
1606
|
+
column_data = np.array(list(range(1, len(self.df) + 1)))
|
|
1607
|
+
|
|
1608
|
+
# Now use the exact same logic as if user provided the data
|
|
1603
1609
|
identity_column_data = None
|
|
1604
1610
|
# Find the identity column data
|
|
1605
1611
|
for other_widget_id in self.dict_columns:
|
|
@@ -1620,11 +1626,55 @@ class ExcelToDictGUI(QMainWindow):
|
|
|
1620
1626
|
result_dict[key_name] = filtered_numerical_ids
|
|
1621
1627
|
else:
|
|
1622
1628
|
result_dict[key_name] = column_data.tolist()
|
|
1629
|
+
|
|
1630
|
+
|
|
1623
1631
|
else:
|
|
1624
1632
|
result_dict[key_name] = column_data.tolist()
|
|
1625
1633
|
else:
|
|
1626
1634
|
result_dict[key_name] = column_data.tolist()
|
|
1627
1635
|
break
|
|
1636
|
+
|
|
1637
|
+
for i in range(self.dict_layout.count()):
|
|
1638
|
+
item = self.dict_layout.itemAt(i)
|
|
1639
|
+
if item and item.widget() and hasattr(item.widget(), 'widget_id'):
|
|
1640
|
+
widget = item.widget()
|
|
1641
|
+
widget_id = widget.widget_id
|
|
1642
|
+
key_name = widget.header_input.text().strip()
|
|
1643
|
+
|
|
1644
|
+
# Skip if already processed (has dropped data) or no key name
|
|
1645
|
+
if widget_id in self.dict_columns or not key_name:
|
|
1646
|
+
continue
|
|
1647
|
+
|
|
1648
|
+
# Handle auto-generation for Node Identities template
|
|
1649
|
+
if property_name == 'Node Identities' and key_name == 'Numerical IDs':
|
|
1650
|
+
|
|
1651
|
+
# Find the identity column data
|
|
1652
|
+
identity_column_data = None
|
|
1653
|
+
for other_widget_id in self.dict_columns:
|
|
1654
|
+
for j in range(self.dict_layout.count()):
|
|
1655
|
+
item_j = self.dict_layout.itemAt(j)
|
|
1656
|
+
if item_j and item_j.widget() and hasattr(item_j.widget(), 'widget_id'):
|
|
1657
|
+
if item_j.widget().widget_id == other_widget_id:
|
|
1658
|
+
other_key_name = item_j.widget().header_input.text().strip()
|
|
1659
|
+
if other_key_name == 'Identity Column':
|
|
1660
|
+
identity_column_data = self.dict_columns[other_widget_id]['data']
|
|
1661
|
+
break
|
|
1662
|
+
if identity_column_data is not None:
|
|
1663
|
+
break
|
|
1664
|
+
|
|
1665
|
+
if identity_column_data is not None:
|
|
1666
|
+
# Auto-generate sequential IDs
|
|
1667
|
+
auto_generated_ids = np.array(list(range(1, len(self.df) + 1)))
|
|
1668
|
+
|
|
1669
|
+
filtered_indices = self.identity_remap_widget.get_filtered_indices(identity_column_data.tolist())
|
|
1670
|
+
|
|
1671
|
+
filtered_numerical_ids = [auto_generated_ids[i] for i in filtered_indices]
|
|
1672
|
+
|
|
1673
|
+
result_dict[key_name] = filtered_numerical_ids
|
|
1674
|
+
else:
|
|
1675
|
+
# Fallback: generate sequential IDs for all rows
|
|
1676
|
+
result_dict[key_name] = list(range(1, len(self.df) + 1))
|
|
1677
|
+
|
|
1628
1678
|
|
|
1629
1679
|
if not result_dict:
|
|
1630
1680
|
QMessageBox.warning(self, "Warning", "No valid dictionary keys defined")
|
nettracer3d/neighborhoods.py
CHANGED
|
@@ -202,6 +202,137 @@ def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
|
|
|
202
202
|
|
|
203
203
|
return embedding
|
|
204
204
|
|
|
205
|
+
def create_community_heatmap(community_intensity, node_community, node_centroids, is_3d=True,
|
|
206
|
+
figsize=(12, 8), point_size=50, alpha=0.7, colorbar_label="Community Intensity"):
|
|
207
|
+
"""
|
|
208
|
+
Create a 2D or 3D heatmap showing nodes colored by their community intensities.
|
|
209
|
+
|
|
210
|
+
Parameters:
|
|
211
|
+
-----------
|
|
212
|
+
community_intensity : dict
|
|
213
|
+
Dictionary mapping community IDs to intensity values
|
|
214
|
+
Keys can be np.int64 or regular ints
|
|
215
|
+
|
|
216
|
+
node_community : dict
|
|
217
|
+
Dictionary mapping node IDs to community IDs
|
|
218
|
+
|
|
219
|
+
node_centroids : dict
|
|
220
|
+
Dictionary mapping node IDs to centroids
|
|
221
|
+
Centroids should be [Z, Y, X] for 3D or [1, Y, X] for pseudo-3D
|
|
222
|
+
|
|
223
|
+
is_3d : bool, default=True
|
|
224
|
+
If True, create 3D plot. If False, create 2D plot.
|
|
225
|
+
|
|
226
|
+
figsize : tuple, default=(12, 8)
|
|
227
|
+
Figure size (width, height)
|
|
228
|
+
|
|
229
|
+
point_size : int, default=50
|
|
230
|
+
Size of scatter plot points
|
|
231
|
+
|
|
232
|
+
alpha : float, default=0.7
|
|
233
|
+
Transparency of points (0-1)
|
|
234
|
+
|
|
235
|
+
colorbar_label : str, default="Community Intensity"
|
|
236
|
+
Label for the colorbar
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
--------
|
|
240
|
+
fig, ax : matplotlib figure and axis objects
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
# Convert numpy int64 keys to regular ints for consistency
|
|
244
|
+
community_intensity_clean = {}
|
|
245
|
+
for k, v in community_intensity.items():
|
|
246
|
+
if hasattr(k, 'item'): # numpy scalar
|
|
247
|
+
community_intensity_clean[k.item()] = v
|
|
248
|
+
else:
|
|
249
|
+
community_intensity_clean[k] = v
|
|
250
|
+
|
|
251
|
+
# Prepare data for plotting
|
|
252
|
+
node_positions = []
|
|
253
|
+
node_intensities = []
|
|
254
|
+
|
|
255
|
+
for node_id, centroid in node_centroids.items():
|
|
256
|
+
try:
|
|
257
|
+
# Get community for this node
|
|
258
|
+
community_id = node_community[node_id]
|
|
259
|
+
|
|
260
|
+
# Convert community_id to regular int if it's numpy
|
|
261
|
+
if hasattr(community_id, 'item'):
|
|
262
|
+
community_id = community_id.item()
|
|
263
|
+
|
|
264
|
+
# Get intensity for this community
|
|
265
|
+
intensity = community_intensity_clean[community_id]
|
|
266
|
+
|
|
267
|
+
node_positions.append(centroid)
|
|
268
|
+
node_intensities.append(intensity)
|
|
269
|
+
except:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
# Convert to numpy arrays
|
|
273
|
+
positions = np.array(node_positions)
|
|
274
|
+
intensities = np.array(node_intensities)
|
|
275
|
+
|
|
276
|
+
# Determine min and max intensities for color scaling
|
|
277
|
+
min_intensity = np.min(intensities)
|
|
278
|
+
max_intensity = np.max(intensities)
|
|
279
|
+
|
|
280
|
+
# Create figure
|
|
281
|
+
fig = plt.figure(figsize=figsize)
|
|
282
|
+
|
|
283
|
+
if is_3d:
|
|
284
|
+
# 3D plot
|
|
285
|
+
ax = fig.add_subplot(111, projection='3d')
|
|
286
|
+
|
|
287
|
+
# Extract coordinates (assuming [Z, Y, X] format)
|
|
288
|
+
z_coords = positions[:, 0]
|
|
289
|
+
y_coords = positions[:, 1]
|
|
290
|
+
x_coords = positions[:, 2]
|
|
291
|
+
|
|
292
|
+
# Create scatter plot
|
|
293
|
+
scatter = ax.scatter(x_coords, y_coords, z_coords,
|
|
294
|
+
c=intensities, s=point_size, alpha=alpha,
|
|
295
|
+
cmap='RdBu_r', vmin=min_intensity, vmax=max_intensity)
|
|
296
|
+
|
|
297
|
+
ax.set_xlabel('X')
|
|
298
|
+
ax.set_ylabel('Y')
|
|
299
|
+
ax.set_zlabel('Z')
|
|
300
|
+
ax.set_title('3D Community Intensity Heatmap')
|
|
301
|
+
|
|
302
|
+
else:
|
|
303
|
+
# 2D plot (using Y, X coordinates, ignoring Z/first dimension)
|
|
304
|
+
ax = fig.add_subplot(111)
|
|
305
|
+
|
|
306
|
+
# Extract Y, X coordinates
|
|
307
|
+
y_coords = positions[:, 1]
|
|
308
|
+
x_coords = positions[:, 2]
|
|
309
|
+
|
|
310
|
+
# Create scatter plot
|
|
311
|
+
scatter = ax.scatter(x_coords, y_coords,
|
|
312
|
+
c=intensities, s=point_size, alpha=alpha,
|
|
313
|
+
cmap='RdBu_r', vmin=min_intensity, vmax=max_intensity)
|
|
314
|
+
|
|
315
|
+
ax.set_xlabel('X')
|
|
316
|
+
ax.set_ylabel('Y')
|
|
317
|
+
ax.set_title('2D Community Intensity Heatmap')
|
|
318
|
+
ax.grid(True, alpha=0.3)
|
|
319
|
+
|
|
320
|
+
# Set origin to top-left (invert Y-axis)
|
|
321
|
+
ax.invert_yaxis()
|
|
322
|
+
|
|
323
|
+
# Add colorbar
|
|
324
|
+
cbar = plt.colorbar(scatter, ax=ax, shrink=0.8)
|
|
325
|
+
cbar.set_label(colorbar_label)
|
|
326
|
+
|
|
327
|
+
# Add text annotations for min/max values
|
|
328
|
+
cbar.ax.text(1.05, 0, f'Min: {min_intensity:.3f}\n(Blue)',
|
|
329
|
+
transform=cbar.ax.transAxes, va='bottom')
|
|
330
|
+
cbar.ax.text(1.05, 1, f'Max: {max_intensity:.3f}\n(Red)',
|
|
331
|
+
transform=cbar.ax.transAxes, va='top')
|
|
332
|
+
|
|
333
|
+
plt.tight_layout()
|
|
334
|
+
plt.show()
|
|
335
|
+
|
|
205
336
|
|
|
206
337
|
|
|
207
338
|
# Example usage:
|
nettracer3d/nettracer.py
CHANGED
|
@@ -3831,6 +3831,41 @@ class Network_3D:
|
|
|
3831
3831
|
self._nodes = self._nodes.astype(np.uint16)
|
|
3832
3832
|
|
|
3833
3833
|
|
|
3834
|
+
def com_by_size(self):
|
|
3835
|
+
"""Reassign communities based on size, starting with 1 for largest."""
|
|
3836
|
+
|
|
3837
|
+
from collections import Counter
|
|
3838
|
+
|
|
3839
|
+
# Convert all community values to regular ints (handles numpy scalars)
|
|
3840
|
+
clean_communities = {
|
|
3841
|
+
node: comm.item() if hasattr(comm, 'item') else comm
|
|
3842
|
+
for node, comm in self.communities.items()
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
# Count community sizes and create mapping in one go
|
|
3846
|
+
community_sizes = Counter(clean_communities.values())
|
|
3847
|
+
|
|
3848
|
+
# Create old->new mapping: sort by size (desc), then by community ID for ties
|
|
3849
|
+
old_to_new = {
|
|
3850
|
+
old_comm: new_comm
|
|
3851
|
+
for new_comm, (old_comm, _) in enumerate(
|
|
3852
|
+
sorted(community_sizes.items(), key=lambda x: (-x[1], x[0])),
|
|
3853
|
+
start=1
|
|
3854
|
+
)
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
# Apply mapping
|
|
3858
|
+
self.communities = {
|
|
3859
|
+
node: old_to_new[comm]
|
|
3860
|
+
for node, comm in clean_communities.items()
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
|
|
3864
|
+
|
|
3865
|
+
|
|
3866
|
+
|
|
3867
|
+
|
|
3868
|
+
|
|
3834
3869
|
def com_to_node(self, targets = None):
|
|
3835
3870
|
|
|
3836
3871
|
def invert_dict(d):
|
|
@@ -4982,7 +5017,7 @@ class Network_3D:
|
|
|
4982
5017
|
|
|
4983
5018
|
return array
|
|
4984
5019
|
|
|
4985
|
-
def community_cells(self, size = 32):
|
|
5020
|
+
def community_cells(self, size = 32, xy_scale = 1, z_scale = 1):
|
|
4986
5021
|
|
|
4987
5022
|
def invert_dict(d):
|
|
4988
5023
|
inverted = {}
|
|
@@ -4991,10 +5026,61 @@ class Network_3D:
|
|
|
4991
5026
|
inverted[value] = key
|
|
4992
5027
|
return inverted
|
|
4993
5028
|
|
|
4994
|
-
|
|
5029
|
+
size_x = int(size * xy_scale)
|
|
5030
|
+
size_z = int(size * z_scale)
|
|
5031
|
+
|
|
5032
|
+
if size_x == size_z:
|
|
5033
|
+
|
|
5034
|
+
com_dict = proximity.partition_objects_into_cells(self.node_centroids, size_x)
|
|
5035
|
+
|
|
5036
|
+
else:
|
|
5037
|
+
|
|
5038
|
+
com_dict = proximity.partition_objects_into_cells(self.node_centroids, (size_z, size_x, size_x))
|
|
4995
5039
|
|
|
4996
5040
|
self.communities = invert_dict(com_dict)
|
|
4997
5041
|
|
|
5042
|
+
def community_heatmap(self, num_nodes = None, is3d = True):
|
|
5043
|
+
|
|
5044
|
+
import math
|
|
5045
|
+
|
|
5046
|
+
def invert_dict(d):
|
|
5047
|
+
inverted = {}
|
|
5048
|
+
for key, value in d.items():
|
|
5049
|
+
inverted.setdefault(value, []).append(key)
|
|
5050
|
+
return inverted
|
|
5051
|
+
|
|
5052
|
+
if num_nodes == None:
|
|
5053
|
+
|
|
5054
|
+
try:
|
|
5055
|
+
num_nodes = len(self.network.nodes())
|
|
5056
|
+
except:
|
|
5057
|
+
try:
|
|
5058
|
+
num_nodes = len(self.node_centroids.keys())
|
|
5059
|
+
except:
|
|
5060
|
+
try:
|
|
5061
|
+
num_nodes = len(self.node_identities.keys())
|
|
5062
|
+
except:
|
|
5063
|
+
try:
|
|
5064
|
+
unique = np.unique(self.nodes)
|
|
5065
|
+
num_nodes = len(unique)
|
|
5066
|
+
if unique[0] == 0:
|
|
5067
|
+
num_nodes -= 1
|
|
5068
|
+
except:
|
|
5069
|
+
return
|
|
5070
|
+
|
|
5071
|
+
coms = invert_dict(self.communities)
|
|
5072
|
+
|
|
5073
|
+
rand_dens = num_nodes / len(coms.keys())
|
|
5074
|
+
|
|
5075
|
+
heat_dict = {}
|
|
5076
|
+
|
|
5077
|
+
for com, nodes in coms.items():
|
|
5078
|
+
heat_dict[com] = math.log(len(nodes)/rand_dens)
|
|
5079
|
+
|
|
5080
|
+
from . import neighborhoods
|
|
5081
|
+
neighborhoods.create_community_heatmap(heat_dict, self.communities, self.node_centroids, is_3d=is3d)
|
|
5082
|
+
|
|
5083
|
+
return heat_dict
|
|
4998
5084
|
|
|
4999
5085
|
|
|
5000
5086
|
|
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -25,7 +25,10 @@ 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
|
+
try:
|
|
29
|
+
from nettracer3d import segmenter_GPU as seg_GPU
|
|
30
|
+
except:
|
|
31
|
+
pass
|
|
29
32
|
from nettracer3d import excelotron
|
|
30
33
|
|
|
31
34
|
|
|
@@ -205,6 +208,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
205
208
|
self.high_button.setChecked(True)
|
|
206
209
|
buttons_layout.addWidget(self.high_button)
|
|
207
210
|
self.highlight = True
|
|
211
|
+
self.needs_mini = False
|
|
208
212
|
|
|
209
213
|
self.pen_button = QPushButton("🖊️")
|
|
210
214
|
self.pen_button.setCheckable(True)
|
|
@@ -394,11 +398,14 @@ class ImageViewerWindow(QMainWindow):
|
|
|
394
398
|
|
|
395
399
|
#self.canvas.mpl_connect('button_press_event', self.on_mouse_click)
|
|
396
400
|
|
|
397
|
-
# Initialize measurement
|
|
401
|
+
# Initialize measurement tracking
|
|
398
402
|
self.measurement_points = [] # List to store point pairs
|
|
399
|
-
self.
|
|
403
|
+
self.angle_measurements = [] # NEW: List to store angle trios
|
|
404
|
+
self.current_point = None # Store first point of current pair/trio
|
|
405
|
+
self.current_second_point = None # Store second point when building trio
|
|
400
406
|
self.current_pair_index = 0 # Track pair numbering
|
|
401
|
-
|
|
407
|
+
self.current_trio_index = 0 # Track trio numbering
|
|
408
|
+
self.measurement_mode = "distance" # "distance" or "angle" mode
|
|
402
409
|
|
|
403
410
|
# Add these new methods for handling neighbors and components (FOR RIGHT CLICKIGN)
|
|
404
411
|
self.show_neighbors_clicked = None
|
|
@@ -664,6 +671,18 @@ class ImageViewerWindow(QMainWindow):
|
|
|
664
671
|
edge_indices (list): List of edge indices to highlight
|
|
665
672
|
"""
|
|
666
673
|
|
|
674
|
+
if not self.high_button.isChecked():
|
|
675
|
+
|
|
676
|
+
if len(self.clicked_values['edges']) > 0:
|
|
677
|
+
self.format_for_upperright_table(self.clicked_values['edges'], title = 'Selected Edges')
|
|
678
|
+
self.needs_mini = True
|
|
679
|
+
if len(self.clicked_values['nodes']) > 0:
|
|
680
|
+
self.format_for_upperright_table(self.clicked_values['nodes'], title = 'Selected Nodes')
|
|
681
|
+
self.needs_mini = True
|
|
682
|
+
|
|
683
|
+
return
|
|
684
|
+
|
|
685
|
+
|
|
667
686
|
def process_chunk(chunk_data, indices_to_check):
|
|
668
687
|
"""Process a single chunk of the array to create highlight mask"""
|
|
669
688
|
mask = np.isin(chunk_data, indices_to_check)
|
|
@@ -828,21 +847,38 @@ class ImageViewerWindow(QMainWindow):
|
|
|
828
847
|
override_obj.triggered.connect(self.handle_override)
|
|
829
848
|
context_menu.addMenu(highlight_menu)
|
|
830
849
|
|
|
831
|
-
# Create
|
|
832
|
-
measure_menu =
|
|
833
|
-
|
|
850
|
+
# Create measurement submenu
|
|
851
|
+
measure_menu = context_menu.addMenu("Measurements")
|
|
852
|
+
|
|
853
|
+
# Distance measurement options
|
|
854
|
+
distance_menu = measure_menu.addMenu("Distance")
|
|
834
855
|
if self.current_point is None:
|
|
835
|
-
|
|
836
|
-
show_point_menu = measure_menu.addAction("Place Measurement Point")
|
|
856
|
+
show_point_menu = distance_menu.addAction("Place First Point")
|
|
837
857
|
show_point_menu.triggered.connect(
|
|
838
|
-
lambda: self.
|
|
858
|
+
lambda: self.place_distance_point(x_idx, y_idx, self.current_slice))
|
|
839
859
|
else:
|
|
840
|
-
|
|
841
|
-
show_point_menu = measure_menu.addAction("Place Second Point")
|
|
860
|
+
show_point_menu = distance_menu.addAction("Place Second Point")
|
|
842
861
|
show_point_menu.triggered.connect(
|
|
843
|
-
lambda: self.
|
|
844
|
-
|
|
845
|
-
|
|
862
|
+
lambda: self.place_distance_point(x_idx, y_idx, self.current_slice))
|
|
863
|
+
|
|
864
|
+
# Angle measurement options
|
|
865
|
+
angle_menu = measure_menu.addMenu("Angle")
|
|
866
|
+
if self.current_point is None:
|
|
867
|
+
angle_first = angle_menu.addAction("Place First Point (A)")
|
|
868
|
+
angle_first.triggered.connect(
|
|
869
|
+
lambda: self.place_angle_point(x_idx, y_idx, self.current_slice))
|
|
870
|
+
elif self.current_second_point is None:
|
|
871
|
+
angle_second = angle_menu.addAction("Place Second Point (B - Vertex)")
|
|
872
|
+
angle_second.triggered.connect(
|
|
873
|
+
lambda: self.place_angle_point(x_idx, y_idx, self.current_slice))
|
|
874
|
+
else:
|
|
875
|
+
angle_third = angle_menu.addAction("Place Third Point (C)")
|
|
876
|
+
angle_third.triggered.connect(
|
|
877
|
+
lambda: self.place_angle_point(x_idx, y_idx, self.current_slice))
|
|
878
|
+
|
|
879
|
+
show_remove_menu = measure_menu.addAction("Remove All Measurements")
|
|
880
|
+
show_remove_menu.triggered.connect(self.handle_remove_all_measurements)
|
|
881
|
+
|
|
846
882
|
context_menu.addMenu(measure_menu)
|
|
847
883
|
|
|
848
884
|
# Connect actions to callbacks
|
|
@@ -860,7 +896,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
860
896
|
if self.highlight_overlay is not None or self.mini_overlay_data is not None:
|
|
861
897
|
highlight_select = context_menu.addAction("Add highlight in network selection")
|
|
862
898
|
highlight_select.triggered.connect(self.handle_highlight_select)
|
|
863
|
-
show_remove_menu.triggered.connect(self.handle_remove_points)
|
|
864
899
|
|
|
865
900
|
cursor_pos = QCursor.pos()
|
|
866
901
|
context_menu.exec(cursor_pos)
|
|
@@ -869,24 +904,25 @@ class ImageViewerWindow(QMainWindow):
|
|
|
869
904
|
pass
|
|
870
905
|
|
|
871
906
|
|
|
872
|
-
def
|
|
873
|
-
"""Place a measurement point
|
|
907
|
+
def place_distance_point(self, x, y, z):
|
|
908
|
+
"""Place a measurement point for distance measurement."""
|
|
874
909
|
if self.current_point is None:
|
|
875
910
|
# This is the first point
|
|
876
911
|
self.current_point = (x, y, z)
|
|
877
912
|
self.ax.plot(x, y, 'yo', markersize=8)
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
color='white', ha='center', va='bottom')
|
|
913
|
+
self.ax.text(x, y+5, f"D{self.current_pair_index}",
|
|
914
|
+
color='yellow', ha='center', va='bottom')
|
|
881
915
|
self.canvas.draw()
|
|
882
|
-
|
|
916
|
+
self.measurement_mode = "distance"
|
|
883
917
|
else:
|
|
884
918
|
# This is the second point
|
|
885
919
|
x1, y1, z1 = self.current_point
|
|
886
920
|
x2, y2, z2 = x, y, z
|
|
887
921
|
|
|
888
922
|
# Calculate distance
|
|
889
|
-
distance = np.sqrt(((x2-x1)*my_network.xy_scale)**2 +
|
|
923
|
+
distance = np.sqrt(((x2-x1)*my_network.xy_scale)**2 +
|
|
924
|
+
((y2-y1)*my_network.xy_scale)**2 +
|
|
925
|
+
((z2-z1)*my_network.z_scale)**2)
|
|
890
926
|
distance2 = np.sqrt(((x2-x1))**2 + ((y2-y1))**2 + ((z2-z1))**2)
|
|
891
927
|
|
|
892
928
|
# Store the point pair
|
|
@@ -900,9 +936,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
900
936
|
|
|
901
937
|
# Draw second point and line
|
|
902
938
|
self.ax.plot(x2, y2, 'yo', markersize=8)
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
color='white', ha='center', va='bottom')
|
|
939
|
+
self.ax.text(x2, y2+5, f"D{self.current_pair_index}",
|
|
940
|
+
color='yellow', ha='center', va='bottom')
|
|
906
941
|
if z1 == z2: # Only draw line if points are on same slice
|
|
907
942
|
self.ax.plot([x1, x2], [y1, y2], 'r--', alpha=0.5)
|
|
908
943
|
self.canvas.draw()
|
|
@@ -913,46 +948,194 @@ class ImageViewerWindow(QMainWindow):
|
|
|
913
948
|
# Reset for next pair
|
|
914
949
|
self.current_point = None
|
|
915
950
|
self.current_pair_index += 1
|
|
951
|
+
self.measurement_mode = "distance"
|
|
952
|
+
|
|
953
|
+
def place_angle_point(self, x, y, z):
|
|
954
|
+
"""Place a measurement point for angle measurement."""
|
|
955
|
+
if self.current_point is None:
|
|
956
|
+
# First point (A)
|
|
957
|
+
self.current_point = (x, y, z)
|
|
958
|
+
self.ax.plot(x, y, 'go', markersize=8)
|
|
959
|
+
self.ax.text(x, y+5, f"A{self.current_trio_index}",
|
|
960
|
+
color='green', ha='center', va='bottom')
|
|
961
|
+
self.canvas.draw()
|
|
962
|
+
self.measurement_mode = "angle"
|
|
963
|
+
|
|
964
|
+
elif self.current_second_point is None:
|
|
965
|
+
# Second point (B - vertex)
|
|
966
|
+
self.current_second_point = (x, y, z)
|
|
967
|
+
x1, y1, z1 = self.current_point
|
|
968
|
+
|
|
969
|
+
self.ax.plot(x, y, 'go', markersize=8)
|
|
970
|
+
self.ax.text(x, y+5, f"B{self.current_trio_index}",
|
|
971
|
+
color='green', ha='center', va='bottom')
|
|
972
|
+
|
|
973
|
+
# Draw line from A to B
|
|
974
|
+
if z1 == z:
|
|
975
|
+
self.ax.plot([x1, x], [y1, y], 'g--', alpha=0.7)
|
|
976
|
+
self.canvas.draw()
|
|
977
|
+
|
|
978
|
+
else:
|
|
979
|
+
# Third point (C)
|
|
980
|
+
x1, y1, z1 = self.current_point # Point A
|
|
981
|
+
x2, y2, z2 = self.current_second_point # Point B (vertex)
|
|
982
|
+
x3, y3, z3 = x, y, z # Point C
|
|
983
|
+
|
|
984
|
+
# Calculate angles and distances
|
|
985
|
+
angle_data = self.calculate_3d_angle(
|
|
986
|
+
(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
# Store the trio
|
|
990
|
+
self.angle_measurements.append({
|
|
991
|
+
'trio_index': self.current_trio_index,
|
|
992
|
+
'point_a': (x1, y1, z1),
|
|
993
|
+
'point_b': (x2, y2, z2), # vertex
|
|
994
|
+
'point_c': (x3, y3, z3),
|
|
995
|
+
**angle_data
|
|
996
|
+
})
|
|
997
|
+
|
|
998
|
+
# Also add the two distances as separate pairs
|
|
999
|
+
dist_ab = np.sqrt(((x2-x1)*my_network.xy_scale)**2 +
|
|
1000
|
+
((y2-y1)*my_network.xy_scale)**2 +
|
|
1001
|
+
((z2-z1)*my_network.z_scale)**2)
|
|
1002
|
+
dist_bc = np.sqrt(((x3-x2)*my_network.xy_scale)**2 +
|
|
1003
|
+
((y3-y2)*my_network.xy_scale)**2 +
|
|
1004
|
+
((z3-z2)*my_network.z_scale)**2)
|
|
1005
|
+
|
|
1006
|
+
dist_ab_voxel = np.sqrt((x2-x1)**2 + (y2-y1)**2 + (z2-z1)**2)
|
|
1007
|
+
dist_bc_voxel = np.sqrt((x3-x2)**2 + (y3-y2)**2 + (z3-z2)**2)
|
|
1008
|
+
|
|
1009
|
+
self.measurement_points.extend([
|
|
1010
|
+
{
|
|
1011
|
+
'pair_index': f"A{self.current_trio_index}-B{self.current_trio_index}",
|
|
1012
|
+
'point1': (x1, y1, z1),
|
|
1013
|
+
'point2': (x2, y2, z2),
|
|
1014
|
+
'distance': dist_ab,
|
|
1015
|
+
'distance2': dist_ab_voxel
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
'pair_index': f"B{self.current_trio_index}-C{self.current_trio_index}",
|
|
1019
|
+
'point1': (x2, y2, z2),
|
|
1020
|
+
'point2': (x3, y3, z3),
|
|
1021
|
+
'distance': dist_bc,
|
|
1022
|
+
'distance2': dist_bc_voxel
|
|
1023
|
+
}
|
|
1024
|
+
])
|
|
1025
|
+
|
|
1026
|
+
# Draw third point and line
|
|
1027
|
+
self.ax.plot(x3, y3, 'go', markersize=8)
|
|
1028
|
+
self.ax.text(x3, y3+5, f"C{self.current_trio_index}",
|
|
1029
|
+
color='green', ha='center', va='bottom')
|
|
1030
|
+
|
|
1031
|
+
if z2 == z3: # Draw line from B to C if on same slice
|
|
1032
|
+
self.ax.plot([x2, x3], [y2, y3], 'g--', alpha=0.7)
|
|
1033
|
+
self.canvas.draw()
|
|
1034
|
+
|
|
1035
|
+
# Update measurement display
|
|
1036
|
+
self.update_measurement_display()
|
|
1037
|
+
|
|
1038
|
+
# Reset for next trio
|
|
1039
|
+
self.current_point = None
|
|
1040
|
+
self.current_second_point = None
|
|
1041
|
+
self.current_trio_index += 1
|
|
1042
|
+
self.measurement_mode = "angle"
|
|
1043
|
+
|
|
1044
|
+
def calculate_3d_angle(self, point_a, point_b, point_c):
|
|
1045
|
+
"""Calculate 3D angle at vertex B between points A-B-C."""
|
|
1046
|
+
x1, y1, z1 = point_a
|
|
1047
|
+
x2, y2, z2 = point_b # vertex
|
|
1048
|
+
x3, y3, z3 = point_c
|
|
1049
|
+
|
|
1050
|
+
# Apply scaling
|
|
1051
|
+
scaled_a = np.array([x1 * my_network.xy_scale, y1 * my_network.xy_scale, z1 * my_network.z_scale])
|
|
1052
|
+
scaled_b = np.array([x2 * my_network.xy_scale, y2 * my_network.xy_scale, z2 * my_network.z_scale])
|
|
1053
|
+
scaled_c = np.array([x3 * my_network.xy_scale, y3 * my_network.xy_scale, z3 * my_network.z_scale])
|
|
1054
|
+
|
|
1055
|
+
# Create vectors from vertex B
|
|
1056
|
+
vec_ba = scaled_a - scaled_b
|
|
1057
|
+
vec_bc = scaled_c - scaled_b
|
|
1058
|
+
|
|
1059
|
+
# Calculate angle using dot product
|
|
1060
|
+
dot_product = np.dot(vec_ba, vec_bc)
|
|
1061
|
+
magnitude_ba = np.linalg.norm(vec_ba)
|
|
1062
|
+
magnitude_bc = np.linalg.norm(vec_bc)
|
|
1063
|
+
|
|
1064
|
+
# Avoid division by zero
|
|
1065
|
+
if magnitude_ba == 0 or magnitude_bc == 0:
|
|
1066
|
+
return {'angle_degrees': 0}
|
|
1067
|
+
|
|
1068
|
+
cos_angle = dot_product / (magnitude_ba * magnitude_bc)
|
|
1069
|
+
cos_angle = np.clip(cos_angle, -1.0, 1.0) # Handle numerical errors
|
|
1070
|
+
|
|
1071
|
+
angle_radians = np.arccos(cos_angle)
|
|
1072
|
+
angle_degrees = np.degrees(angle_radians)
|
|
1073
|
+
|
|
1074
|
+
return {'angle_degrees': angle_degrees}
|
|
916
1075
|
|
|
917
|
-
def
|
|
918
|
-
"""Remove all measurement points."""
|
|
1076
|
+
def handle_remove_all_measurements(self):
|
|
1077
|
+
"""Remove all measurement points and angles."""
|
|
919
1078
|
self.measurement_points = []
|
|
1079
|
+
self.angle_measurements = []
|
|
920
1080
|
self.current_point = None
|
|
1081
|
+
self.current_second_point = None
|
|
921
1082
|
self.current_pair_index = 0
|
|
1083
|
+
self.current_trio_index = 0
|
|
1084
|
+
self.measurement_mode = "distance"
|
|
922
1085
|
self.update_display()
|
|
923
1086
|
self.update_measurement_display()
|
|
924
1087
|
|
|
925
|
-
# Modify the update_measurement_display method:
|
|
926
1088
|
def update_measurement_display(self):
|
|
927
1089
|
"""Update the measurement information display in the top right widget."""
|
|
1090
|
+
# Distance measurements
|
|
928
1091
|
if not self.measurement_points:
|
|
929
|
-
|
|
930
|
-
df = pd.DataFrame()
|
|
1092
|
+
distance_df = pd.DataFrame()
|
|
931
1093
|
else:
|
|
932
|
-
|
|
933
|
-
data = []
|
|
1094
|
+
distance_data = []
|
|
934
1095
|
for point in self.measurement_points:
|
|
935
1096
|
x1, y1, z1 = point['point1']
|
|
936
1097
|
x2, y2, z2 = point['point2']
|
|
937
|
-
|
|
1098
|
+
distance_data.append({
|
|
938
1099
|
'Pair ID': point['pair_index'],
|
|
939
1100
|
'Point 1 (X,Y,Z)': f"({x1:.1f}, {y1:.1f}, {z1})",
|
|
940
1101
|
'Point 2 (X,Y,Z)': f"({x2:.1f}, {y2:.1f}, {z2})",
|
|
941
1102
|
'Scaled Distance': f"{point['distance']:.2f}",
|
|
942
1103
|
'Voxel Distance': f"{point['distance2']:.2f}"
|
|
943
1104
|
})
|
|
944
|
-
|
|
1105
|
+
distance_df = pd.DataFrame(distance_data)
|
|
945
1106
|
|
|
946
|
-
#
|
|
947
|
-
|
|
948
|
-
|
|
1107
|
+
# Angle measurements
|
|
1108
|
+
if not self.angle_measurements:
|
|
1109
|
+
angle_df = pd.DataFrame()
|
|
1110
|
+
else:
|
|
1111
|
+
angle_data = []
|
|
1112
|
+
for angle in self.angle_measurements:
|
|
1113
|
+
xa, ya, za = angle['point_a']
|
|
1114
|
+
xb, yb, zb = angle['point_b']
|
|
1115
|
+
xc, yc, zc = angle['point_c']
|
|
1116
|
+
angle_data.append({
|
|
1117
|
+
'Trio ID': f"A{angle['trio_index']}-B{angle['trio_index']}-C{angle['trio_index']}",
|
|
1118
|
+
'Point A (X,Y,Z)': f"({xa:.1f}, {ya:.1f}, {za})",
|
|
1119
|
+
'Point B (X,Y,Z)': f"({xb:.1f}, {yb:.1f}, {zb})",
|
|
1120
|
+
'Point C (X,Y,Z)': f"({xc:.1f}, {yc:.1f}, {zc})",
|
|
1121
|
+
'Angle (°)': f"{angle['angle_degrees']:.1f}"
|
|
1122
|
+
})
|
|
1123
|
+
angle_df = pd.DataFrame(angle_data)
|
|
949
1124
|
|
|
950
|
-
#
|
|
951
|
-
|
|
1125
|
+
# Create tables
|
|
1126
|
+
if not distance_df.empty:
|
|
1127
|
+
distance_table = CustomTableView(self)
|
|
1128
|
+
distance_table.setModel(PandasModel(distance_df))
|
|
1129
|
+
self.tabbed_data.add_table("Distance Measurements", distance_table)
|
|
1130
|
+
for column in range(distance_table.model().columnCount(None)):
|
|
1131
|
+
distance_table.resizeColumnToContents(column)
|
|
952
1132
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1133
|
+
if not angle_df.empty:
|
|
1134
|
+
angle_table = CustomTableView(self)
|
|
1135
|
+
angle_table.setModel(PandasModel(angle_df))
|
|
1136
|
+
self.tabbed_data.add_table("Angle Measurements", angle_table)
|
|
1137
|
+
for column in range(angle_table.model().columnCount(None)):
|
|
1138
|
+
angle_table.resizeColumnToContents(column)
|
|
956
1139
|
|
|
957
1140
|
|
|
958
1141
|
def show_network_table(self):
|
|
@@ -1732,6 +1915,12 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1732
1915
|
self.highlight = self.high_button.isChecked()
|
|
1733
1916
|
current_xlim = self.ax.get_xlim() if hasattr(self, 'ax') and self.ax.get_xlim() != (0, 1) else None
|
|
1734
1917
|
current_ylim = self.ax.get_ylim() if hasattr(self, 'ax') and self.ax.get_ylim() != (0, 1) else None
|
|
1918
|
+
|
|
1919
|
+
if self.high_button.isChecked():
|
|
1920
|
+
if self.highlight_overlay is None and ((len(self.clicked_values['nodes']) + len(self.clicked_values['edges'])) > 0):
|
|
1921
|
+
if self.needs_mini:
|
|
1922
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1923
|
+
self.needs_mini = False
|
|
1735
1924
|
|
|
1736
1925
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
1737
1926
|
|
|
@@ -2704,6 +2893,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2704
2893
|
network_menu = analysis_menu.addMenu("Network")
|
|
2705
2894
|
netshow_action = network_menu.addAction("Show Network")
|
|
2706
2895
|
netshow_action.triggered.connect(self.show_netshow_dialog)
|
|
2896
|
+
report_action = network_menu.addAction("Generic Network Report")
|
|
2897
|
+
report_action.triggered.connect(self.handle_report)
|
|
2707
2898
|
partition_action = network_menu.addAction("Community Partition + Generic Community Stats")
|
|
2708
2899
|
partition_action.triggered.connect(self.show_partition_dialog)
|
|
2709
2900
|
com_identity_action = network_menu.addAction("Identity Makeup of Network Communities (and UMAP)")
|
|
@@ -2723,8 +2914,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2723
2914
|
degree_dist_action.triggered.connect(self.show_degree_dist_dialog)
|
|
2724
2915
|
neighbor_id_action = stats_menu.addAction("Identity Distribution of Neighbors")
|
|
2725
2916
|
neighbor_id_action.triggered.connect(self.show_neighbor_id_dialog)
|
|
2726
|
-
ripley_action = stats_menu.addAction("Clustering Analysis")
|
|
2917
|
+
ripley_action = stats_menu.addAction("Ripley Clustering Analysis")
|
|
2727
2918
|
ripley_action.triggered.connect(self.show_ripley_dialog)
|
|
2919
|
+
heatmap_action = stats_menu.addAction("Community Cluster Heatmap")
|
|
2920
|
+
heatmap_action.triggered.connect(self.show_heatmap_dialog)
|
|
2728
2921
|
vol_action = stats_menu.addAction("Calculate Volumes")
|
|
2729
2922
|
vol_action.triggered.connect(self.volumes)
|
|
2730
2923
|
rad_action = stats_menu.addAction("Calculate Radii")
|
|
@@ -4396,6 +4589,44 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4396
4589
|
dialog = NetShowDialog(self)
|
|
4397
4590
|
dialog.exec()
|
|
4398
4591
|
|
|
4592
|
+
def handle_report(self):
|
|
4593
|
+
|
|
4594
|
+
def invert_dict(d):
|
|
4595
|
+
inverted = {}
|
|
4596
|
+
for key, value in d.items():
|
|
4597
|
+
inverted.setdefault(value, []).append(key)
|
|
4598
|
+
return inverted
|
|
4599
|
+
|
|
4600
|
+
stats = {}
|
|
4601
|
+
|
|
4602
|
+
try:
|
|
4603
|
+
# Basic graph properties
|
|
4604
|
+
stats['num_nodes'] = my_network.network.number_of_nodes()
|
|
4605
|
+
stats['num_edges'] = my_network.network.number_of_edges()
|
|
4606
|
+
except:
|
|
4607
|
+
pass
|
|
4608
|
+
|
|
4609
|
+
try:
|
|
4610
|
+
idens = invert_dict(my_network.node_identities)
|
|
4611
|
+
|
|
4612
|
+
for iden, nodes in idens.items():
|
|
4613
|
+
stats[f'num_nodes_{iden}'] = len(nodes)
|
|
4614
|
+
except:
|
|
4615
|
+
pass
|
|
4616
|
+
|
|
4617
|
+
try:
|
|
4618
|
+
|
|
4619
|
+
coms = invert_dict(my_network.communities)
|
|
4620
|
+
|
|
4621
|
+
for com, nodes in coms.items():
|
|
4622
|
+
stats[f'num_nodes_community_{com}'] = len(nodes)
|
|
4623
|
+
except:
|
|
4624
|
+
pass
|
|
4625
|
+
|
|
4626
|
+
self.format_for_upperright_table(stats, title = 'Network Report')
|
|
4627
|
+
|
|
4628
|
+
|
|
4629
|
+
|
|
4399
4630
|
def show_partition_dialog(self):
|
|
4400
4631
|
dialog = PartitionDialog(self)
|
|
4401
4632
|
dialog.exec()
|
|
@@ -4431,6 +4662,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4431
4662
|
dialog = RipleyDialog(self)
|
|
4432
4663
|
dialog.exec()
|
|
4433
4664
|
|
|
4665
|
+
def show_heatmap_dialog(self):
|
|
4666
|
+
dialog = HeatmapDialog(self)
|
|
4667
|
+
dialog.exec()
|
|
4668
|
+
|
|
4434
4669
|
def show_random_dialog(self):
|
|
4435
4670
|
dialog = RandomDialog(self)
|
|
4436
4671
|
dialog.exec()
|
|
@@ -5660,8 +5895,7 @@ class ArbitraryDialog(QDialog):
|
|
|
5660
5895
|
|
|
5661
5896
|
except Exception as e:
|
|
5662
5897
|
QMessageBox.critical(self, "Error", f"Error processing selections: {str(e)}")
|
|
5663
|
-
|
|
5664
|
-
print(traceback.format_exc())
|
|
5898
|
+
|
|
5665
5899
|
|
|
5666
5900
|
class Show3dDialog(QDialog):
|
|
5667
5901
|
def __init__(self, parent=None):
|
|
@@ -6223,7 +6457,7 @@ class ComNeighborDialog(QDialog):
|
|
|
6223
6457
|
layout.addRow("Min Community Size to be grouped (Smaller communities will be placed in neighborhood 0 - does not apply if empty)", self.limit)
|
|
6224
6458
|
|
|
6225
6459
|
# Add Run button
|
|
6226
|
-
run_button = QPushButton("Get
|
|
6460
|
+
run_button = QPushButton("Get Communities")
|
|
6227
6461
|
run_button.clicked.connect(self.run)
|
|
6228
6462
|
layout.addWidget(run_button)
|
|
6229
6463
|
|
|
@@ -6277,6 +6511,12 @@ class ComCellDialog(QDialog):
|
|
|
6277
6511
|
self.size = QLineEdit("")
|
|
6278
6512
|
layout.addRow("Cell Size:", self.size)
|
|
6279
6513
|
|
|
6514
|
+
self.xy_scale = QLineEdit(f"{my_network.xy_scale}")
|
|
6515
|
+
layout.addRow("xy scale:", self.xy_scale)
|
|
6516
|
+
|
|
6517
|
+
self.z_scale = QLineEdit(f"{my_network.z_scale}")
|
|
6518
|
+
layout.addRow("z scale:", self.z_scale)
|
|
6519
|
+
|
|
6280
6520
|
# Add Run button
|
|
6281
6521
|
run_button = QPushButton("Get Neighborhoods (Note this overwrites current communities - save your coms first)")
|
|
6282
6522
|
run_button.clicked.connect(self.run)
|
|
@@ -6286,7 +6526,9 @@ class ComCellDialog(QDialog):
|
|
|
6286
6526
|
|
|
6287
6527
|
try:
|
|
6288
6528
|
|
|
6289
|
-
size =
|
|
6529
|
+
size = float(self.size.text()) if self.size.text().strip() else None
|
|
6530
|
+
xy_scale = float(self.xy_scale.text()) if self.xy_scale.text().strip() else 1
|
|
6531
|
+
z_scale = float(self.z_scale.text()) if self.z_scale.text().strip() else 1
|
|
6290
6532
|
|
|
6291
6533
|
if size is None:
|
|
6292
6534
|
return
|
|
@@ -6296,7 +6538,7 @@ class ComCellDialog(QDialog):
|
|
|
6296
6538
|
if my_network.node_centroids is None:
|
|
6297
6539
|
return
|
|
6298
6540
|
|
|
6299
|
-
my_network.community_cells(size = size)
|
|
6541
|
+
my_network.community_cells(size = size, xy_scale = xy_scale, z_scale = z_scale)
|
|
6300
6542
|
|
|
6301
6543
|
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID')
|
|
6302
6544
|
|
|
@@ -6577,6 +6819,60 @@ class RipleyDialog(QDialog):
|
|
|
6577
6819
|
print(traceback.format_exc())
|
|
6578
6820
|
print(f"Error: {e}")
|
|
6579
6821
|
|
|
6822
|
+
class HeatmapDialog(QDialog):
|
|
6823
|
+
|
|
6824
|
+
def __init__(self, parent = None):
|
|
6825
|
+
|
|
6826
|
+
super().__init__(parent)
|
|
6827
|
+
self.setWindowTitle("Heatmap Parameters")
|
|
6828
|
+
self.setModal(True)
|
|
6829
|
+
|
|
6830
|
+
layout = QFormLayout(self)
|
|
6831
|
+
|
|
6832
|
+
self.nodecount = QLineEdit("")
|
|
6833
|
+
layout.addRow("(Optional) Total Number of Nodes?:", self.nodecount)
|
|
6834
|
+
|
|
6835
|
+
|
|
6836
|
+
# stats checkbox (default True)
|
|
6837
|
+
self.is3d = QPushButton("3D")
|
|
6838
|
+
self.is3d.setCheckable(True)
|
|
6839
|
+
self.is3d.setChecked(True)
|
|
6840
|
+
layout.addRow("Use 3D Plot (uncheck for 2D)?:", self.is3d)
|
|
6841
|
+
|
|
6842
|
+
|
|
6843
|
+
# Add Run button
|
|
6844
|
+
run_button = QPushButton("Run")
|
|
6845
|
+
run_button.clicked.connect(self.run)
|
|
6846
|
+
layout.addWidget(run_button)
|
|
6847
|
+
|
|
6848
|
+
def run(self):
|
|
6849
|
+
|
|
6850
|
+
nodecount = int(self.nodecount.text()) if self.nodecount.text().strip() else None
|
|
6851
|
+
|
|
6852
|
+
is3d = self.is3d.isChecked()
|
|
6853
|
+
|
|
6854
|
+
|
|
6855
|
+
if my_network.communities is None:
|
|
6856
|
+
if my_network.network is not None:
|
|
6857
|
+
self.parent().show_partition_dialog()
|
|
6858
|
+
else:
|
|
6859
|
+
self.parent().handle_com_cell()
|
|
6860
|
+
if my_network.communities is None:
|
|
6861
|
+
return
|
|
6862
|
+
|
|
6863
|
+
heat_dict = my_network.community_heatmap(num_nodes = nodecount, is3d = is3d)
|
|
6864
|
+
|
|
6865
|
+
self.parent().format_for_upperright_table(heat_dict, metric='Community', value='ln(Predicted Community Nodecount/Actual)', title="Community Heatmap")
|
|
6866
|
+
|
|
6867
|
+
self.accept()
|
|
6868
|
+
|
|
6869
|
+
|
|
6870
|
+
|
|
6871
|
+
|
|
6872
|
+
|
|
6873
|
+
|
|
6874
|
+
|
|
6875
|
+
|
|
6580
6876
|
class RandomDialog(QDialog):
|
|
6581
6877
|
|
|
6582
6878
|
def __init__(self, parent=None):
|
|
@@ -7997,7 +8293,7 @@ class MachineWindow(QMainWindow):
|
|
|
7997
8293
|
if not GPU:
|
|
7998
8294
|
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=False)
|
|
7999
8295
|
else:
|
|
8000
|
-
self.segmenter =
|
|
8296
|
+
self.segmenter = seg_GPU.InteractiveSegmenter(active_data)
|
|
8001
8297
|
|
|
8002
8298
|
self.segmentation_worker = None
|
|
8003
8299
|
|
|
@@ -8103,7 +8399,7 @@ class MachineWindow(QMainWindow):
|
|
|
8103
8399
|
if self.GPU.isChecked():
|
|
8104
8400
|
|
|
8105
8401
|
try:
|
|
8106
|
-
self.segmenter =
|
|
8402
|
+
self.segmenter = seg_GPU.InteractiveSegmenter(active_data)
|
|
8107
8403
|
print("Using GPU")
|
|
8108
8404
|
except:
|
|
8109
8405
|
self.GPU.setChecked(False)
|
|
@@ -10361,6 +10657,12 @@ class ModifyDialog(QDialog):
|
|
|
10361
10657
|
self.isolate.setChecked(False)
|
|
10362
10658
|
layout.addRow("Isolate connections between two specific node types (if assigned)?:", self.isolate)
|
|
10363
10659
|
|
|
10660
|
+
# isolate checkbox (default false)
|
|
10661
|
+
self.com_sizes = QPushButton("Communities By Size")
|
|
10662
|
+
self.com_sizes.setCheckable(True)
|
|
10663
|
+
self.com_sizes.setChecked(False)
|
|
10664
|
+
layout.addRow("Rearrange Community IDs by size?:", self.com_sizes)
|
|
10665
|
+
|
|
10364
10666
|
# Community collapse checkbox (default False)
|
|
10365
10667
|
self.comcollapse = QPushButton("Communities -> nodes")
|
|
10366
10668
|
self.comcollapse.setCheckable(True)
|
|
@@ -10403,6 +10705,7 @@ class ModifyDialog(QDialog):
|
|
|
10403
10705
|
isolate = self.isolate.isChecked()
|
|
10404
10706
|
comcollapse = self.comcollapse.isChecked()
|
|
10405
10707
|
remove = self.remove.isChecked()
|
|
10708
|
+
com_size = self.com_sizes.isChecked()
|
|
10406
10709
|
|
|
10407
10710
|
|
|
10408
10711
|
if isolate and my_network.node_identities is not None:
|
|
@@ -10453,6 +10756,14 @@ class ModifyDialog(QDialog):
|
|
|
10453
10756
|
self.parent().format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
10454
10757
|
except:
|
|
10455
10758
|
pass
|
|
10759
|
+
if com_size:
|
|
10760
|
+
if my_network.communities is None:
|
|
10761
|
+
self.parent().show_partition_dialog()
|
|
10762
|
+
if my_network.communities is None:
|
|
10763
|
+
return
|
|
10764
|
+
my_network.com_by_size()
|
|
10765
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'Community', 'Node Communities')
|
|
10766
|
+
|
|
10456
10767
|
if comcollapse:
|
|
10457
10768
|
if my_network.communities is None:
|
|
10458
10769
|
self.parent().show_partition_dialog()
|
|
@@ -10482,6 +10793,8 @@ class ModifyDialog(QDialog):
|
|
|
10482
10793
|
self.accept()
|
|
10483
10794
|
|
|
10484
10795
|
except Exception as e:
|
|
10796
|
+
import traceback
|
|
10797
|
+
print(traceback.format_exc())
|
|
10485
10798
|
print(f"An error occurred: {e}")
|
|
10486
10799
|
|
|
10487
10800
|
|
|
@@ -10612,85 +10925,107 @@ class CalcAllDialog(QDialog):
|
|
|
10612
10925
|
prev_fastdil = False
|
|
10613
10926
|
prev_overlays = False
|
|
10614
10927
|
prev_updates = True
|
|
10615
|
-
|
|
10928
|
+
|
|
10616
10929
|
def __init__(self, parent=None):
|
|
10617
10930
|
super().__init__(parent)
|
|
10618
|
-
self.setWindowTitle("Calculate
|
|
10931
|
+
self.setWindowTitle("Calculate Connectivity Network Parameters")
|
|
10619
10932
|
self.setModal(True)
|
|
10620
10933
|
|
|
10621
|
-
|
|
10934
|
+
# Main layout
|
|
10935
|
+
main_layout = QVBoxLayout(self)
|
|
10622
10936
|
|
|
10623
|
-
#
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
layout.addRow("Output Directory:", self.directory)
|
|
10937
|
+
# Important Parameters Group
|
|
10938
|
+
important_group = QGroupBox("Important Parameters")
|
|
10939
|
+
important_layout = QFormLayout(important_group)
|
|
10627
10940
|
|
|
10628
|
-
# Load previous values for all inputs
|
|
10629
10941
|
self.xy_scale = QLineEdit(f'{my_network.xy_scale}')
|
|
10630
|
-
|
|
10942
|
+
important_layout.addRow("xy_scale:", self.xy_scale)
|
|
10631
10943
|
|
|
10632
10944
|
self.z_scale = QLineEdit(f'{my_network.z_scale}')
|
|
10633
|
-
|
|
10634
|
-
|
|
10945
|
+
important_layout.addRow("z_scale:", self.z_scale)
|
|
10946
|
+
|
|
10635
10947
|
self.search = QLineEdit(self.prev_search)
|
|
10636
10948
|
self.search.setPlaceholderText("Leave empty for None")
|
|
10637
|
-
|
|
10638
|
-
|
|
10949
|
+
important_layout.addRow("Node Search (float):", self.search)
|
|
10950
|
+
|
|
10639
10951
|
self.diledge = QLineEdit(self.prev_diledge)
|
|
10640
10952
|
self.diledge.setPlaceholderText("Leave empty for None")
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
self.
|
|
10644
|
-
self.
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10953
|
+
important_layout.addRow("Edge Reconnection Distance (float):", self.diledge)
|
|
10954
|
+
|
|
10955
|
+
self.label_nodes = QPushButton("Label")
|
|
10956
|
+
self.label_nodes.setCheckable(True)
|
|
10957
|
+
self.label_nodes.setChecked(self.prev_label_nodes)
|
|
10958
|
+
important_layout.addRow("Re-Label Nodes (WARNING - OVERRIDES ANY CURRENT LABELS):", self.label_nodes)
|
|
10959
|
+
|
|
10960
|
+
main_layout.addWidget(important_group)
|
|
10961
|
+
|
|
10962
|
+
# Optional Parameters Group
|
|
10963
|
+
optional_group = QGroupBox("Optional Parameters")
|
|
10964
|
+
optional_layout = QFormLayout(optional_group)
|
|
10965
|
+
|
|
10651
10966
|
self.other_nodes = QLineEdit(self.prev_other_nodes)
|
|
10652
10967
|
self.other_nodes.setPlaceholderText("Leave empty for None")
|
|
10653
|
-
|
|
10654
|
-
|
|
10968
|
+
optional_layout.addRow("Filepath or directory containing additional node images:", self.other_nodes)
|
|
10969
|
+
|
|
10655
10970
|
self.remove_trunk = QLineEdit(self.prev_remove_trunk)
|
|
10656
10971
|
self.remove_trunk.setPlaceholderText("Leave empty for 0")
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
# Load previous button states
|
|
10660
|
-
self.gpu = QPushButton("GPU")
|
|
10661
|
-
self.gpu.setCheckable(True)
|
|
10662
|
-
self.gpu.setChecked(self.prev_gpu)
|
|
10663
|
-
layout.addRow("Use GPU:", self.gpu)
|
|
10664
|
-
|
|
10665
|
-
self.label_nodes = QPushButton("Label")
|
|
10666
|
-
self.label_nodes.setCheckable(True)
|
|
10667
|
-
self.label_nodes.setChecked(self.prev_label_nodes)
|
|
10668
|
-
layout.addRow("Re-Label Nodes (WARNING - OVERRIDES ANY CURRENT LABELS):", self.label_nodes)
|
|
10669
|
-
|
|
10972
|
+
optional_layout.addRow("Times to remove edge trunks (int):", self.remove_trunk)
|
|
10973
|
+
|
|
10670
10974
|
self.inners = QPushButton("Inner Edges")
|
|
10671
10975
|
self.inners.setCheckable(True)
|
|
10672
10976
|
self.inners.setChecked(self.prev_inners)
|
|
10673
|
-
|
|
10674
|
-
|
|
10977
|
+
optional_layout.addRow("Use Inner Edges:", self.inners)
|
|
10978
|
+
|
|
10979
|
+
main_layout.addWidget(optional_group)
|
|
10980
|
+
|
|
10981
|
+
# Speed Up Options Group
|
|
10982
|
+
speedup_group = QGroupBox("Speed Up Options")
|
|
10983
|
+
speedup_layout = QFormLayout(speedup_group)
|
|
10984
|
+
|
|
10985
|
+
self.down_factor = QLineEdit(self.prev_down_factor)
|
|
10986
|
+
self.down_factor.setPlaceholderText("Leave empty for None")
|
|
10987
|
+
speedup_layout.addRow("Downsample for Centroids (int):", self.down_factor)
|
|
10988
|
+
|
|
10989
|
+
self.GPU_downsample = QLineEdit(self.prev_GPU_downsample)
|
|
10990
|
+
self.GPU_downsample.setPlaceholderText("Leave empty for None")
|
|
10991
|
+
speedup_layout.addRow("Downsample for Distance Transform (GPU) (int):", self.GPU_downsample)
|
|
10992
|
+
|
|
10993
|
+
self.gpu = QPushButton("GPU")
|
|
10994
|
+
self.gpu.setCheckable(True)
|
|
10995
|
+
self.gpu.setChecked(self.prev_gpu)
|
|
10996
|
+
speedup_layout.addRow("Use GPU:", self.gpu)
|
|
10997
|
+
|
|
10675
10998
|
self.fastdil = QPushButton("Fast Dilate")
|
|
10676
10999
|
self.fastdil.setCheckable(True)
|
|
10677
11000
|
self.fastdil.setChecked(self.prev_fastdil)
|
|
10678
|
-
|
|
10679
|
-
|
|
11001
|
+
speedup_layout.addRow("Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
11002
|
+
|
|
11003
|
+
main_layout.addWidget(speedup_group)
|
|
11004
|
+
|
|
11005
|
+
# Output Options Group
|
|
11006
|
+
output_group = QGroupBox("Output Options")
|
|
11007
|
+
output_layout = QFormLayout(output_group)
|
|
11008
|
+
|
|
11009
|
+
self.directory = QLineEdit(self.prev_directory)
|
|
11010
|
+
self.directory.setPlaceholderText("Will Have to Save Manually If Empty")
|
|
11011
|
+
output_layout.addRow("Output Directory:", self.directory)
|
|
11012
|
+
|
|
10680
11013
|
self.overlays = QPushButton("Overlays")
|
|
10681
11014
|
self.overlays.setCheckable(True)
|
|
10682
11015
|
self.overlays.setChecked(self.prev_overlays)
|
|
10683
|
-
|
|
10684
|
-
|
|
11016
|
+
output_layout.addRow("Generate Overlays:", self.overlays)
|
|
11017
|
+
|
|
10685
11018
|
self.update = QPushButton("Update")
|
|
10686
11019
|
self.update.setCheckable(True)
|
|
10687
11020
|
self.update.setChecked(self.prev_updates)
|
|
10688
|
-
|
|
11021
|
+
output_layout.addRow("Update Node/Edge in NetTracer3D:", self.update)
|
|
11022
|
+
|
|
11023
|
+
main_layout.addWidget(output_group)
|
|
10689
11024
|
|
|
10690
11025
|
# Add Run button
|
|
10691
11026
|
run_button = QPushButton("Run Calculate All")
|
|
10692
11027
|
run_button.clicked.connect(self.run_calc_all)
|
|
10693
|
-
|
|
11028
|
+
main_layout.addWidget(run_button)
|
|
10694
11029
|
|
|
10695
11030
|
def run_calc_all(self):
|
|
10696
11031
|
|
|
@@ -10867,65 +11202,87 @@ class CalcAllDialog(QDialog):
|
|
|
10867
11202
|
f"Error running calculate all: {str(e)}"
|
|
10868
11203
|
)
|
|
10869
11204
|
|
|
10870
|
-
class ProxDialog(QDialog):
|
|
10871
11205
|
|
|
11206
|
+
class ProxDialog(QDialog):
|
|
10872
11207
|
def __init__(self, parent=None):
|
|
10873
11208
|
super().__init__(parent)
|
|
10874
11209
|
self.setWindowTitle("Calculate Proximity Network")
|
|
10875
11210
|
self.setModal(True)
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
11211
|
+
|
|
11212
|
+
# Main layout
|
|
11213
|
+
main_layout = QVBoxLayout(self)
|
|
11214
|
+
|
|
11215
|
+
# Important Parameters Group
|
|
11216
|
+
important_group = QGroupBox("Important Parameters")
|
|
11217
|
+
important_layout = QFormLayout(important_group)
|
|
11218
|
+
|
|
10884
11219
|
self.search = QLineEdit()
|
|
10885
11220
|
self.search.setPlaceholderText("search")
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
# Load previous values for all inputs
|
|
11221
|
+
important_layout.addRow("Search Region Distance? (enter true value corresponding to scaling, ie in microns):", self.search)
|
|
11222
|
+
|
|
10889
11223
|
self.xy_scale = QLineEdit(f"{my_network.xy_scale}")
|
|
10890
|
-
|
|
11224
|
+
important_layout.addRow("xy_scale:", self.xy_scale)
|
|
10891
11225
|
|
|
10892
11226
|
self.z_scale = QLineEdit(f"{my_network.z_scale}")
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
11227
|
+
important_layout.addRow("z_scale:", self.z_scale)
|
|
11228
|
+
|
|
11229
|
+
main_layout.addWidget(important_group)
|
|
11230
|
+
|
|
11231
|
+
# Mode Group
|
|
11232
|
+
mode_group = QGroupBox("Mode")
|
|
11233
|
+
mode_layout = QFormLayout(mode_group)
|
|
11234
|
+
|
|
10896
11235
|
self.mode_selector = QComboBox()
|
|
10897
11236
|
self.mode_selector.addItems(["From Centroids (fast but ignores shape - use for small or spherical objects - search STARTS at centroid)", "From Morphological Shape (slower but preserves shape - use for oddly shaped objects - search STARTS at object border)"])
|
|
10898
11237
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
self.fastdil = QPushButton("Fast Dilate")
|
|
10902
|
-
self.fastdil.setCheckable(True)
|
|
10903
|
-
self.fastdil.setChecked(False)
|
|
10904
|
-
layout.addRow("(If using morphological) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
10905
|
-
|
|
11238
|
+
mode_layout.addRow("Execution Mode:", self.mode_selector)
|
|
11239
|
+
|
|
10906
11240
|
if my_network.node_identities is not None:
|
|
10907
11241
|
self.id_selector = QComboBox()
|
|
10908
11242
|
# Add all options from id dictionary
|
|
10909
11243
|
self.id_selector.addItems(['None'] + list(set(my_network.node_identities.values())))
|
|
10910
11244
|
self.id_selector.setCurrentIndex(0) # Default to Mode 1
|
|
10911
|
-
|
|
11245
|
+
mode_layout.addRow("Create Networks only from a specific node identity?:", self.id_selector)
|
|
10912
11246
|
else:
|
|
10913
11247
|
self.id_selector = None
|
|
10914
|
-
|
|
11248
|
+
|
|
11249
|
+
main_layout.addWidget(mode_group)
|
|
11250
|
+
|
|
11251
|
+
# Output Options Group
|
|
11252
|
+
output_group = QGroupBox("Output Options")
|
|
11253
|
+
output_layout = QFormLayout(output_group)
|
|
11254
|
+
|
|
11255
|
+
self.directory = QLineEdit('')
|
|
11256
|
+
self.directory.setPlaceholderText("Leave empty for 'my_network'")
|
|
11257
|
+
output_layout.addRow("Output Directory:", self.directory)
|
|
11258
|
+
|
|
10915
11259
|
self.overlays = QPushButton("Overlays")
|
|
10916
11260
|
self.overlays.setCheckable(True)
|
|
10917
11261
|
self.overlays.setChecked(True)
|
|
10918
|
-
|
|
10919
|
-
|
|
11262
|
+
output_layout.addRow("Generate Overlays:", self.overlays)
|
|
11263
|
+
|
|
10920
11264
|
self.populate = QPushButton("Populate Nodes from Centroids?")
|
|
10921
11265
|
self.populate.setCheckable(True)
|
|
10922
11266
|
self.populate.setChecked(False)
|
|
10923
|
-
|
|
10924
|
-
|
|
11267
|
+
output_layout.addRow("If using centroid search:", self.populate)
|
|
11268
|
+
|
|
11269
|
+
main_layout.addWidget(output_group)
|
|
11270
|
+
|
|
11271
|
+
# Speed Up Options Group
|
|
11272
|
+
speedup_group = QGroupBox("Speed Up Options")
|
|
11273
|
+
speedup_layout = QFormLayout(speedup_group)
|
|
11274
|
+
|
|
11275
|
+
self.fastdil = QPushButton("Fast Dilate")
|
|
11276
|
+
self.fastdil.setCheckable(True)
|
|
11277
|
+
self.fastdil.setChecked(False)
|
|
11278
|
+
speedup_layout.addRow("(If using morphological) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
11279
|
+
|
|
11280
|
+
main_layout.addWidget(speedup_group)
|
|
11281
|
+
|
|
10925
11282
|
# Add Run button
|
|
10926
11283
|
run_button = QPushButton("Run Proximity Network")
|
|
10927
11284
|
run_button.clicked.connect(self.prox)
|
|
10928
|
-
|
|
11285
|
+
main_layout.addWidget(run_button)
|
|
10929
11286
|
|
|
10930
11287
|
def prox(self):
|
|
10931
11288
|
|
nettracer3d/proximity.py
CHANGED
|
@@ -723,7 +723,7 @@ def partition_objects_into_cells(object_centroids, cell_size):
|
|
|
723
723
|
cell_indices[1] * num_cells[2] +
|
|
724
724
|
cell_indices[2])
|
|
725
725
|
|
|
726
|
-
cell_assignments[int(cell_number)].append(label)
|
|
726
|
+
cell_assignments[int(cell_number)].append(int(label))
|
|
727
727
|
|
|
728
728
|
# Convert defaultdict to regular dict and sort keys
|
|
729
729
|
return dict(sorted(cell_assignments.items()))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nettracer3d
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.9
|
|
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/
|
|
@@ -73,6 +73,6 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
|
|
|
73
73
|
|
|
74
74
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
75
75
|
|
|
76
|
-
-- Version 0.7.
|
|
76
|
+
-- Version 0.7.9 Updates --
|
|
77
77
|
|
|
78
|
-
*
|
|
78
|
+
* The GPU segmenter was being imported regardless of GPU status, causing the program to fail without cupy (which should be optional), fixed that.
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
nettracer3d/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
nettracer3d/community_extractor.py,sha256=BrONCLRLYUdMfLe_018AQi0k0J7xwVahc_lmsOO5Pwo,23086
|
|
3
|
-
nettracer3d/excelotron.py,sha256=
|
|
3
|
+
nettracer3d/excelotron.py,sha256=lS5vnpoOGZWp7fdqVpTPqeC-mUKrfwDrWHfx4PQ7Uzg,71384
|
|
4
4
|
nettracer3d/modularity.py,sha256=O9OeKbjD3v6gSFz9K2GzP6LsxlpQaPfeJbM1pyIEigw,21788
|
|
5
5
|
nettracer3d/morphology.py,sha256=jyDjYzrZ4LvI5jOyw8DLsxmo-i5lpqHsejYpW7Tq7Mo,19786
|
|
6
|
-
nettracer3d/neighborhoods.py,sha256=
|
|
7
|
-
nettracer3d/nettracer.py,sha256=
|
|
8
|
-
nettracer3d/nettracer_gui.py,sha256=
|
|
6
|
+
nettracer3d/neighborhoods.py,sha256=kkKR8m6Gjw34cDd_mytAIwLxqvuNBtQb2hU4JuBY9pI,12301
|
|
7
|
+
nettracer3d/nettracer.py,sha256=M1KFIPg7WCzm8BXQOreuEVhgjg0PpLKRg4Y88DyVuK8,225843
|
|
8
|
+
nettracer3d/nettracer_gui.py,sha256=wdx_9putVHl0H4NIf_Bnmj0epgDb7OEMZYXb8pD8cYo,468815
|
|
9
9
|
nettracer3d/network_analysis.py,sha256=h-5yzUWdE0hcWYy8wcBA5LV1bRhdqiMnKbQLrRzb1Sw,41443
|
|
10
10
|
nettracer3d/network_draw.py,sha256=F7fw6Pcf4qWOhdKwLmhwqWdschbDlHzwCVolQC9imeU,14117
|
|
11
11
|
nettracer3d/node_draw.py,sha256=k3sCTfUCJs3aH1C1q1gTNxDz9EAQbBd1hsUIJajxRx8,9823
|
|
12
|
-
nettracer3d/proximity.py,sha256=
|
|
12
|
+
nettracer3d/proximity.py,sha256=5n8qxqxmmMtq5bqVpSkqw3EefuZIyGdLybVs18D3ZNg,27996
|
|
13
13
|
nettracer3d/run.py,sha256=xYeaAc8FCx8MuzTGyL3NR3mK7WZzffAYAH23bNRZYO4,127
|
|
14
14
|
nettracer3d/segmenter.py,sha256=BD9vxnblDKXfmR8hLP_iVqqfVngH6opz4Q7V6sxc2KM,60062
|
|
15
15
|
nettracer3d/segmenter_GPU.py,sha256=Fqr0Za6X2ss4rfaziqOhvhBfbGDPHkHw6fVxs39lZaU,53862
|
|
16
16
|
nettracer3d/simple_network.py,sha256=Ft_81VhVQ3rqoXvuYnsckXuxCcQSJfakhOfkFaclxZY,9340
|
|
17
17
|
nettracer3d/smart_dilate.py,sha256=DOEOQq9ig6-AO4MpqAG0CqrGDFqw5_UBeqfSedqHk28,25933
|
|
18
|
-
nettracer3d-0.7.
|
|
19
|
-
nettracer3d-0.7.
|
|
20
|
-
nettracer3d-0.7.
|
|
21
|
-
nettracer3d-0.7.
|
|
22
|
-
nettracer3d-0.7.
|
|
23
|
-
nettracer3d-0.7.
|
|
18
|
+
nettracer3d-0.7.9.dist-info/licenses/LICENSE,sha256=gM207DhJjWrxLuEWXl0Qz5ISbtWDmADfjHp3yC2XISs,888
|
|
19
|
+
nettracer3d-0.7.9.dist-info/METADATA,sha256=3EyPBVXOMeZqA9D4vTpBvhOo6wOj_5KHW5lacpuApEk,4254
|
|
20
|
+
nettracer3d-0.7.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
nettracer3d-0.7.9.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
|
|
22
|
+
nettracer3d-0.7.9.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
|
|
23
|
+
nettracer3d-0.7.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|