nettracer3d 0.7.7__py3-none-any.whl → 0.7.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nettracer3d/excelotron.py +51 -1
- nettracer3d/neighborhoods.py +131 -0
- nettracer3d/nettracer.py +88 -2
- nettracer3d/nettracer_gui.py +478 -124
- nettracer3d/proximity.py +1 -1
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.8.dist-info}/METADATA +12 -4
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.8.dist-info}/RECORD +11 -11
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.8.dist-info}/WHEEL +0 -0
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.8.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.8.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.7.7.dist-info → nettracer3d-0.7.8.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
|
@@ -205,6 +205,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
205
205
|
self.high_button.setChecked(True)
|
|
206
206
|
buttons_layout.addWidget(self.high_button)
|
|
207
207
|
self.highlight = True
|
|
208
|
+
self.needs_mini = False
|
|
208
209
|
|
|
209
210
|
self.pen_button = QPushButton("🖊️")
|
|
210
211
|
self.pen_button.setCheckable(True)
|
|
@@ -394,11 +395,14 @@ class ImageViewerWindow(QMainWindow):
|
|
|
394
395
|
|
|
395
396
|
#self.canvas.mpl_connect('button_press_event', self.on_mouse_click)
|
|
396
397
|
|
|
397
|
-
# Initialize measurement
|
|
398
|
+
# Initialize measurement tracking
|
|
398
399
|
self.measurement_points = [] # List to store point pairs
|
|
399
|
-
self.
|
|
400
|
+
self.angle_measurements = [] # NEW: List to store angle trios
|
|
401
|
+
self.current_point = None # Store first point of current pair/trio
|
|
402
|
+
self.current_second_point = None # Store second point when building trio
|
|
400
403
|
self.current_pair_index = 0 # Track pair numbering
|
|
401
|
-
|
|
404
|
+
self.current_trio_index = 0 # Track trio numbering
|
|
405
|
+
self.measurement_mode = "distance" # "distance" or "angle" mode
|
|
402
406
|
|
|
403
407
|
# Add these new methods for handling neighbors and components (FOR RIGHT CLICKIGN)
|
|
404
408
|
self.show_neighbors_clicked = None
|
|
@@ -664,6 +668,18 @@ class ImageViewerWindow(QMainWindow):
|
|
|
664
668
|
edge_indices (list): List of edge indices to highlight
|
|
665
669
|
"""
|
|
666
670
|
|
|
671
|
+
if not self.high_button.isChecked():
|
|
672
|
+
|
|
673
|
+
if len(self.clicked_values['edges']) > 0:
|
|
674
|
+
self.format_for_upperright_table(self.clicked_values['edges'], title = 'Selected Edges')
|
|
675
|
+
self.needs_mini = True
|
|
676
|
+
if len(self.clicked_values['nodes']) > 0:
|
|
677
|
+
self.format_for_upperright_table(self.clicked_values['nodes'], title = 'Selected Nodes')
|
|
678
|
+
self.needs_mini = True
|
|
679
|
+
|
|
680
|
+
return
|
|
681
|
+
|
|
682
|
+
|
|
667
683
|
def process_chunk(chunk_data, indices_to_check):
|
|
668
684
|
"""Process a single chunk of the array to create highlight mask"""
|
|
669
685
|
mask = np.isin(chunk_data, indices_to_check)
|
|
@@ -828,21 +844,38 @@ class ImageViewerWindow(QMainWindow):
|
|
|
828
844
|
override_obj.triggered.connect(self.handle_override)
|
|
829
845
|
context_menu.addMenu(highlight_menu)
|
|
830
846
|
|
|
831
|
-
# Create
|
|
832
|
-
measure_menu =
|
|
833
|
-
|
|
847
|
+
# Create measurement submenu
|
|
848
|
+
measure_menu = context_menu.addMenu("Measurements")
|
|
849
|
+
|
|
850
|
+
# Distance measurement options
|
|
851
|
+
distance_menu = measure_menu.addMenu("Distance")
|
|
834
852
|
if self.current_point is None:
|
|
835
|
-
|
|
836
|
-
show_point_menu = measure_menu.addAction("Place Measurement Point")
|
|
853
|
+
show_point_menu = distance_menu.addAction("Place First Point")
|
|
837
854
|
show_point_menu.triggered.connect(
|
|
838
|
-
lambda: self.
|
|
855
|
+
lambda: self.place_distance_point(x_idx, y_idx, self.current_slice))
|
|
839
856
|
else:
|
|
840
|
-
|
|
841
|
-
show_point_menu = measure_menu.addAction("Place Second Point")
|
|
857
|
+
show_point_menu = distance_menu.addAction("Place Second Point")
|
|
842
858
|
show_point_menu.triggered.connect(
|
|
843
|
-
lambda: self.
|
|
844
|
-
|
|
845
|
-
|
|
859
|
+
lambda: self.place_distance_point(x_idx, y_idx, self.current_slice))
|
|
860
|
+
|
|
861
|
+
# Angle measurement options
|
|
862
|
+
angle_menu = measure_menu.addMenu("Angle")
|
|
863
|
+
if self.current_point is None:
|
|
864
|
+
angle_first = angle_menu.addAction("Place First Point (A)")
|
|
865
|
+
angle_first.triggered.connect(
|
|
866
|
+
lambda: self.place_angle_point(x_idx, y_idx, self.current_slice))
|
|
867
|
+
elif self.current_second_point is None:
|
|
868
|
+
angle_second = angle_menu.addAction("Place Second Point (B - Vertex)")
|
|
869
|
+
angle_second.triggered.connect(
|
|
870
|
+
lambda: self.place_angle_point(x_idx, y_idx, self.current_slice))
|
|
871
|
+
else:
|
|
872
|
+
angle_third = angle_menu.addAction("Place Third Point (C)")
|
|
873
|
+
angle_third.triggered.connect(
|
|
874
|
+
lambda: self.place_angle_point(x_idx, y_idx, self.current_slice))
|
|
875
|
+
|
|
876
|
+
show_remove_menu = measure_menu.addAction("Remove All Measurements")
|
|
877
|
+
show_remove_menu.triggered.connect(self.handle_remove_all_measurements)
|
|
878
|
+
|
|
846
879
|
context_menu.addMenu(measure_menu)
|
|
847
880
|
|
|
848
881
|
# Connect actions to callbacks
|
|
@@ -860,7 +893,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
860
893
|
if self.highlight_overlay is not None or self.mini_overlay_data is not None:
|
|
861
894
|
highlight_select = context_menu.addAction("Add highlight in network selection")
|
|
862
895
|
highlight_select.triggered.connect(self.handle_highlight_select)
|
|
863
|
-
show_remove_menu.triggered.connect(self.handle_remove_points)
|
|
864
896
|
|
|
865
897
|
cursor_pos = QCursor.pos()
|
|
866
898
|
context_menu.exec(cursor_pos)
|
|
@@ -869,24 +901,25 @@ class ImageViewerWindow(QMainWindow):
|
|
|
869
901
|
pass
|
|
870
902
|
|
|
871
903
|
|
|
872
|
-
def
|
|
873
|
-
"""Place a measurement point
|
|
904
|
+
def place_distance_point(self, x, y, z):
|
|
905
|
+
"""Place a measurement point for distance measurement."""
|
|
874
906
|
if self.current_point is None:
|
|
875
907
|
# This is the first point
|
|
876
908
|
self.current_point = (x, y, z)
|
|
877
909
|
self.ax.plot(x, y, 'yo', markersize=8)
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
color='white', ha='center', va='bottom')
|
|
910
|
+
self.ax.text(x, y+5, f"D{self.current_pair_index}",
|
|
911
|
+
color='yellow', ha='center', va='bottom')
|
|
881
912
|
self.canvas.draw()
|
|
882
|
-
|
|
913
|
+
self.measurement_mode = "distance"
|
|
883
914
|
else:
|
|
884
915
|
# This is the second point
|
|
885
916
|
x1, y1, z1 = self.current_point
|
|
886
917
|
x2, y2, z2 = x, y, z
|
|
887
918
|
|
|
888
919
|
# Calculate distance
|
|
889
|
-
distance = np.sqrt(((x2-x1)*my_network.xy_scale)**2 +
|
|
920
|
+
distance = np.sqrt(((x2-x1)*my_network.xy_scale)**2 +
|
|
921
|
+
((y2-y1)*my_network.xy_scale)**2 +
|
|
922
|
+
((z2-z1)*my_network.z_scale)**2)
|
|
890
923
|
distance2 = np.sqrt(((x2-x1))**2 + ((y2-y1))**2 + ((z2-z1))**2)
|
|
891
924
|
|
|
892
925
|
# Store the point pair
|
|
@@ -900,9 +933,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
900
933
|
|
|
901
934
|
# Draw second point and line
|
|
902
935
|
self.ax.plot(x2, y2, 'yo', markersize=8)
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
color='white', ha='center', va='bottom')
|
|
936
|
+
self.ax.text(x2, y2+5, f"D{self.current_pair_index}",
|
|
937
|
+
color='yellow', ha='center', va='bottom')
|
|
906
938
|
if z1 == z2: # Only draw line if points are on same slice
|
|
907
939
|
self.ax.plot([x1, x2], [y1, y2], 'r--', alpha=0.5)
|
|
908
940
|
self.canvas.draw()
|
|
@@ -913,46 +945,194 @@ class ImageViewerWindow(QMainWindow):
|
|
|
913
945
|
# Reset for next pair
|
|
914
946
|
self.current_point = None
|
|
915
947
|
self.current_pair_index += 1
|
|
948
|
+
self.measurement_mode = "distance"
|
|
949
|
+
|
|
950
|
+
def place_angle_point(self, x, y, z):
|
|
951
|
+
"""Place a measurement point for angle measurement."""
|
|
952
|
+
if self.current_point is None:
|
|
953
|
+
# First point (A)
|
|
954
|
+
self.current_point = (x, y, z)
|
|
955
|
+
self.ax.plot(x, y, 'go', markersize=8)
|
|
956
|
+
self.ax.text(x, y+5, f"A{self.current_trio_index}",
|
|
957
|
+
color='green', ha='center', va='bottom')
|
|
958
|
+
self.canvas.draw()
|
|
959
|
+
self.measurement_mode = "angle"
|
|
960
|
+
|
|
961
|
+
elif self.current_second_point is None:
|
|
962
|
+
# Second point (B - vertex)
|
|
963
|
+
self.current_second_point = (x, y, z)
|
|
964
|
+
x1, y1, z1 = self.current_point
|
|
965
|
+
|
|
966
|
+
self.ax.plot(x, y, 'go', markersize=8)
|
|
967
|
+
self.ax.text(x, y+5, f"B{self.current_trio_index}",
|
|
968
|
+
color='green', ha='center', va='bottom')
|
|
969
|
+
|
|
970
|
+
# Draw line from A to B
|
|
971
|
+
if z1 == z:
|
|
972
|
+
self.ax.plot([x1, x], [y1, y], 'g--', alpha=0.7)
|
|
973
|
+
self.canvas.draw()
|
|
974
|
+
|
|
975
|
+
else:
|
|
976
|
+
# Third point (C)
|
|
977
|
+
x1, y1, z1 = self.current_point # Point A
|
|
978
|
+
x2, y2, z2 = self.current_second_point # Point B (vertex)
|
|
979
|
+
x3, y3, z3 = x, y, z # Point C
|
|
980
|
+
|
|
981
|
+
# Calculate angles and distances
|
|
982
|
+
angle_data = self.calculate_3d_angle(
|
|
983
|
+
(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
# Store the trio
|
|
987
|
+
self.angle_measurements.append({
|
|
988
|
+
'trio_index': self.current_trio_index,
|
|
989
|
+
'point_a': (x1, y1, z1),
|
|
990
|
+
'point_b': (x2, y2, z2), # vertex
|
|
991
|
+
'point_c': (x3, y3, z3),
|
|
992
|
+
**angle_data
|
|
993
|
+
})
|
|
994
|
+
|
|
995
|
+
# Also add the two distances as separate pairs
|
|
996
|
+
dist_ab = np.sqrt(((x2-x1)*my_network.xy_scale)**2 +
|
|
997
|
+
((y2-y1)*my_network.xy_scale)**2 +
|
|
998
|
+
((z2-z1)*my_network.z_scale)**2)
|
|
999
|
+
dist_bc = np.sqrt(((x3-x2)*my_network.xy_scale)**2 +
|
|
1000
|
+
((y3-y2)*my_network.xy_scale)**2 +
|
|
1001
|
+
((z3-z2)*my_network.z_scale)**2)
|
|
1002
|
+
|
|
1003
|
+
dist_ab_voxel = np.sqrt((x2-x1)**2 + (y2-y1)**2 + (z2-z1)**2)
|
|
1004
|
+
dist_bc_voxel = np.sqrt((x3-x2)**2 + (y3-y2)**2 + (z3-z2)**2)
|
|
1005
|
+
|
|
1006
|
+
self.measurement_points.extend([
|
|
1007
|
+
{
|
|
1008
|
+
'pair_index': f"A{self.current_trio_index}-B{self.current_trio_index}",
|
|
1009
|
+
'point1': (x1, y1, z1),
|
|
1010
|
+
'point2': (x2, y2, z2),
|
|
1011
|
+
'distance': dist_ab,
|
|
1012
|
+
'distance2': dist_ab_voxel
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
'pair_index': f"B{self.current_trio_index}-C{self.current_trio_index}",
|
|
1016
|
+
'point1': (x2, y2, z2),
|
|
1017
|
+
'point2': (x3, y3, z3),
|
|
1018
|
+
'distance': dist_bc,
|
|
1019
|
+
'distance2': dist_bc_voxel
|
|
1020
|
+
}
|
|
1021
|
+
])
|
|
1022
|
+
|
|
1023
|
+
# Draw third point and line
|
|
1024
|
+
self.ax.plot(x3, y3, 'go', markersize=8)
|
|
1025
|
+
self.ax.text(x3, y3+5, f"C{self.current_trio_index}",
|
|
1026
|
+
color='green', ha='center', va='bottom')
|
|
1027
|
+
|
|
1028
|
+
if z2 == z3: # Draw line from B to C if on same slice
|
|
1029
|
+
self.ax.plot([x2, x3], [y2, y3], 'g--', alpha=0.7)
|
|
1030
|
+
self.canvas.draw()
|
|
1031
|
+
|
|
1032
|
+
# Update measurement display
|
|
1033
|
+
self.update_measurement_display()
|
|
1034
|
+
|
|
1035
|
+
# Reset for next trio
|
|
1036
|
+
self.current_point = None
|
|
1037
|
+
self.current_second_point = None
|
|
1038
|
+
self.current_trio_index += 1
|
|
1039
|
+
self.measurement_mode = "angle"
|
|
1040
|
+
|
|
1041
|
+
def calculate_3d_angle(self, point_a, point_b, point_c):
|
|
1042
|
+
"""Calculate 3D angle at vertex B between points A-B-C."""
|
|
1043
|
+
x1, y1, z1 = point_a
|
|
1044
|
+
x2, y2, z2 = point_b # vertex
|
|
1045
|
+
x3, y3, z3 = point_c
|
|
1046
|
+
|
|
1047
|
+
# Apply scaling
|
|
1048
|
+
scaled_a = np.array([x1 * my_network.xy_scale, y1 * my_network.xy_scale, z1 * my_network.z_scale])
|
|
1049
|
+
scaled_b = np.array([x2 * my_network.xy_scale, y2 * my_network.xy_scale, z2 * my_network.z_scale])
|
|
1050
|
+
scaled_c = np.array([x3 * my_network.xy_scale, y3 * my_network.xy_scale, z3 * my_network.z_scale])
|
|
1051
|
+
|
|
1052
|
+
# Create vectors from vertex B
|
|
1053
|
+
vec_ba = scaled_a - scaled_b
|
|
1054
|
+
vec_bc = scaled_c - scaled_b
|
|
1055
|
+
|
|
1056
|
+
# Calculate angle using dot product
|
|
1057
|
+
dot_product = np.dot(vec_ba, vec_bc)
|
|
1058
|
+
magnitude_ba = np.linalg.norm(vec_ba)
|
|
1059
|
+
magnitude_bc = np.linalg.norm(vec_bc)
|
|
1060
|
+
|
|
1061
|
+
# Avoid division by zero
|
|
1062
|
+
if magnitude_ba == 0 or magnitude_bc == 0:
|
|
1063
|
+
return {'angle_degrees': 0}
|
|
1064
|
+
|
|
1065
|
+
cos_angle = dot_product / (magnitude_ba * magnitude_bc)
|
|
1066
|
+
cos_angle = np.clip(cos_angle, -1.0, 1.0) # Handle numerical errors
|
|
1067
|
+
|
|
1068
|
+
angle_radians = np.arccos(cos_angle)
|
|
1069
|
+
angle_degrees = np.degrees(angle_radians)
|
|
1070
|
+
|
|
1071
|
+
return {'angle_degrees': angle_degrees}
|
|
916
1072
|
|
|
917
|
-
def
|
|
918
|
-
"""Remove all measurement points."""
|
|
1073
|
+
def handle_remove_all_measurements(self):
|
|
1074
|
+
"""Remove all measurement points and angles."""
|
|
919
1075
|
self.measurement_points = []
|
|
1076
|
+
self.angle_measurements = []
|
|
920
1077
|
self.current_point = None
|
|
1078
|
+
self.current_second_point = None
|
|
921
1079
|
self.current_pair_index = 0
|
|
1080
|
+
self.current_trio_index = 0
|
|
1081
|
+
self.measurement_mode = "distance"
|
|
922
1082
|
self.update_display()
|
|
923
1083
|
self.update_measurement_display()
|
|
924
1084
|
|
|
925
|
-
# Modify the update_measurement_display method:
|
|
926
1085
|
def update_measurement_display(self):
|
|
927
1086
|
"""Update the measurement information display in the top right widget."""
|
|
1087
|
+
# Distance measurements
|
|
928
1088
|
if not self.measurement_points:
|
|
929
|
-
|
|
930
|
-
df = pd.DataFrame()
|
|
1089
|
+
distance_df = pd.DataFrame()
|
|
931
1090
|
else:
|
|
932
|
-
|
|
933
|
-
data = []
|
|
1091
|
+
distance_data = []
|
|
934
1092
|
for point in self.measurement_points:
|
|
935
1093
|
x1, y1, z1 = point['point1']
|
|
936
1094
|
x2, y2, z2 = point['point2']
|
|
937
|
-
|
|
1095
|
+
distance_data.append({
|
|
938
1096
|
'Pair ID': point['pair_index'],
|
|
939
1097
|
'Point 1 (X,Y,Z)': f"({x1:.1f}, {y1:.1f}, {z1})",
|
|
940
1098
|
'Point 2 (X,Y,Z)': f"({x2:.1f}, {y2:.1f}, {z2})",
|
|
941
1099
|
'Scaled Distance': f"{point['distance']:.2f}",
|
|
942
1100
|
'Voxel Distance': f"{point['distance2']:.2f}"
|
|
943
1101
|
})
|
|
944
|
-
|
|
1102
|
+
distance_df = pd.DataFrame(distance_data)
|
|
945
1103
|
|
|
946
|
-
#
|
|
947
|
-
|
|
948
|
-
|
|
1104
|
+
# Angle measurements
|
|
1105
|
+
if not self.angle_measurements:
|
|
1106
|
+
angle_df = pd.DataFrame()
|
|
1107
|
+
else:
|
|
1108
|
+
angle_data = []
|
|
1109
|
+
for angle in self.angle_measurements:
|
|
1110
|
+
xa, ya, za = angle['point_a']
|
|
1111
|
+
xb, yb, zb = angle['point_b']
|
|
1112
|
+
xc, yc, zc = angle['point_c']
|
|
1113
|
+
angle_data.append({
|
|
1114
|
+
'Trio ID': f"A{angle['trio_index']}-B{angle['trio_index']}-C{angle['trio_index']}",
|
|
1115
|
+
'Point A (X,Y,Z)': f"({xa:.1f}, {ya:.1f}, {za})",
|
|
1116
|
+
'Point B (X,Y,Z)': f"({xb:.1f}, {yb:.1f}, {zb})",
|
|
1117
|
+
'Point C (X,Y,Z)': f"({xc:.1f}, {yc:.1f}, {zc})",
|
|
1118
|
+
'Angle (°)': f"{angle['angle_degrees']:.1f}"
|
|
1119
|
+
})
|
|
1120
|
+
angle_df = pd.DataFrame(angle_data)
|
|
949
1121
|
|
|
950
|
-
#
|
|
951
|
-
|
|
1122
|
+
# Create tables
|
|
1123
|
+
if not distance_df.empty:
|
|
1124
|
+
distance_table = CustomTableView(self)
|
|
1125
|
+
distance_table.setModel(PandasModel(distance_df))
|
|
1126
|
+
self.tabbed_data.add_table("Distance Measurements", distance_table)
|
|
1127
|
+
for column in range(distance_table.model().columnCount(None)):
|
|
1128
|
+
distance_table.resizeColumnToContents(column)
|
|
952
1129
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1130
|
+
if not angle_df.empty:
|
|
1131
|
+
angle_table = CustomTableView(self)
|
|
1132
|
+
angle_table.setModel(PandasModel(angle_df))
|
|
1133
|
+
self.tabbed_data.add_table("Angle Measurements", angle_table)
|
|
1134
|
+
for column in range(angle_table.model().columnCount(None)):
|
|
1135
|
+
angle_table.resizeColumnToContents(column)
|
|
956
1136
|
|
|
957
1137
|
|
|
958
1138
|
def show_network_table(self):
|
|
@@ -1732,6 +1912,12 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1732
1912
|
self.highlight = self.high_button.isChecked()
|
|
1733
1913
|
current_xlim = self.ax.get_xlim() if hasattr(self, 'ax') and self.ax.get_xlim() != (0, 1) else None
|
|
1734
1914
|
current_ylim = self.ax.get_ylim() if hasattr(self, 'ax') and self.ax.get_ylim() != (0, 1) else None
|
|
1915
|
+
|
|
1916
|
+
if self.high_button.isChecked():
|
|
1917
|
+
if self.highlight_overlay is None and ((len(self.clicked_values['nodes']) + len(self.clicked_values['edges'])) > 0):
|
|
1918
|
+
if self.needs_mini:
|
|
1919
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1920
|
+
self.needs_mini = False
|
|
1735
1921
|
|
|
1736
1922
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
1737
1923
|
|
|
@@ -2704,6 +2890,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2704
2890
|
network_menu = analysis_menu.addMenu("Network")
|
|
2705
2891
|
netshow_action = network_menu.addAction("Show Network")
|
|
2706
2892
|
netshow_action.triggered.connect(self.show_netshow_dialog)
|
|
2893
|
+
report_action = network_menu.addAction("Generic Network Report")
|
|
2894
|
+
report_action.triggered.connect(self.handle_report)
|
|
2707
2895
|
partition_action = network_menu.addAction("Community Partition + Generic Community Stats")
|
|
2708
2896
|
partition_action.triggered.connect(self.show_partition_dialog)
|
|
2709
2897
|
com_identity_action = network_menu.addAction("Identity Makeup of Network Communities (and UMAP)")
|
|
@@ -2723,8 +2911,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2723
2911
|
degree_dist_action.triggered.connect(self.show_degree_dist_dialog)
|
|
2724
2912
|
neighbor_id_action = stats_menu.addAction("Identity Distribution of Neighbors")
|
|
2725
2913
|
neighbor_id_action.triggered.connect(self.show_neighbor_id_dialog)
|
|
2726
|
-
ripley_action = stats_menu.addAction("Clustering Analysis")
|
|
2914
|
+
ripley_action = stats_menu.addAction("Ripley Clustering Analysis")
|
|
2727
2915
|
ripley_action.triggered.connect(self.show_ripley_dialog)
|
|
2916
|
+
heatmap_action = stats_menu.addAction("Community Cluster Heatmap")
|
|
2917
|
+
heatmap_action.triggered.connect(self.show_heatmap_dialog)
|
|
2728
2918
|
vol_action = stats_menu.addAction("Calculate Volumes")
|
|
2729
2919
|
vol_action.triggered.connect(self.volumes)
|
|
2730
2920
|
rad_action = stats_menu.addAction("Calculate Radii")
|
|
@@ -4396,6 +4586,44 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4396
4586
|
dialog = NetShowDialog(self)
|
|
4397
4587
|
dialog.exec()
|
|
4398
4588
|
|
|
4589
|
+
def handle_report(self):
|
|
4590
|
+
|
|
4591
|
+
def invert_dict(d):
|
|
4592
|
+
inverted = {}
|
|
4593
|
+
for key, value in d.items():
|
|
4594
|
+
inverted.setdefault(value, []).append(key)
|
|
4595
|
+
return inverted
|
|
4596
|
+
|
|
4597
|
+
stats = {}
|
|
4598
|
+
|
|
4599
|
+
try:
|
|
4600
|
+
# Basic graph properties
|
|
4601
|
+
stats['num_nodes'] = my_network.network.number_of_nodes()
|
|
4602
|
+
stats['num_edges'] = my_network.network.number_of_edges()
|
|
4603
|
+
except:
|
|
4604
|
+
pass
|
|
4605
|
+
|
|
4606
|
+
try:
|
|
4607
|
+
idens = invert_dict(my_network.node_identities)
|
|
4608
|
+
|
|
4609
|
+
for iden, nodes in idens.items():
|
|
4610
|
+
stats[f'num_nodes_{iden}'] = len(nodes)
|
|
4611
|
+
except:
|
|
4612
|
+
pass
|
|
4613
|
+
|
|
4614
|
+
try:
|
|
4615
|
+
|
|
4616
|
+
coms = invert_dict(my_network.communities)
|
|
4617
|
+
|
|
4618
|
+
for com, nodes in coms.items():
|
|
4619
|
+
stats[f'num_nodes_community_{com}'] = len(nodes)
|
|
4620
|
+
except:
|
|
4621
|
+
pass
|
|
4622
|
+
|
|
4623
|
+
self.format_for_upperright_table(stats, title = 'Network Report')
|
|
4624
|
+
|
|
4625
|
+
|
|
4626
|
+
|
|
4399
4627
|
def show_partition_dialog(self):
|
|
4400
4628
|
dialog = PartitionDialog(self)
|
|
4401
4629
|
dialog.exec()
|
|
@@ -4431,6 +4659,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4431
4659
|
dialog = RipleyDialog(self)
|
|
4432
4660
|
dialog.exec()
|
|
4433
4661
|
|
|
4662
|
+
def show_heatmap_dialog(self):
|
|
4663
|
+
dialog = HeatmapDialog(self)
|
|
4664
|
+
dialog.exec()
|
|
4665
|
+
|
|
4434
4666
|
def show_random_dialog(self):
|
|
4435
4667
|
dialog = RandomDialog(self)
|
|
4436
4668
|
dialog.exec()
|
|
@@ -5660,8 +5892,7 @@ class ArbitraryDialog(QDialog):
|
|
|
5660
5892
|
|
|
5661
5893
|
except Exception as e:
|
|
5662
5894
|
QMessageBox.critical(self, "Error", f"Error processing selections: {str(e)}")
|
|
5663
|
-
|
|
5664
|
-
print(traceback.format_exc())
|
|
5895
|
+
|
|
5665
5896
|
|
|
5666
5897
|
class Show3dDialog(QDialog):
|
|
5667
5898
|
def __init__(self, parent=None):
|
|
@@ -6223,7 +6454,7 @@ class ComNeighborDialog(QDialog):
|
|
|
6223
6454
|
layout.addRow("Min Community Size to be grouped (Smaller communities will be placed in neighborhood 0 - does not apply if empty)", self.limit)
|
|
6224
6455
|
|
|
6225
6456
|
# Add Run button
|
|
6226
|
-
run_button = QPushButton("Get
|
|
6457
|
+
run_button = QPushButton("Get Communities")
|
|
6227
6458
|
run_button.clicked.connect(self.run)
|
|
6228
6459
|
layout.addWidget(run_button)
|
|
6229
6460
|
|
|
@@ -6277,6 +6508,12 @@ class ComCellDialog(QDialog):
|
|
|
6277
6508
|
self.size = QLineEdit("")
|
|
6278
6509
|
layout.addRow("Cell Size:", self.size)
|
|
6279
6510
|
|
|
6511
|
+
self.xy_scale = QLineEdit(f"{my_network.xy_scale}")
|
|
6512
|
+
layout.addRow("xy scale:", self.xy_scale)
|
|
6513
|
+
|
|
6514
|
+
self.z_scale = QLineEdit(f"{my_network.z_scale}")
|
|
6515
|
+
layout.addRow("z scale:", self.z_scale)
|
|
6516
|
+
|
|
6280
6517
|
# Add Run button
|
|
6281
6518
|
run_button = QPushButton("Get Neighborhoods (Note this overwrites current communities - save your coms first)")
|
|
6282
6519
|
run_button.clicked.connect(self.run)
|
|
@@ -6286,7 +6523,9 @@ class ComCellDialog(QDialog):
|
|
|
6286
6523
|
|
|
6287
6524
|
try:
|
|
6288
6525
|
|
|
6289
|
-
size =
|
|
6526
|
+
size = float(self.size.text()) if self.size.text().strip() else None
|
|
6527
|
+
xy_scale = float(self.xy_scale.text()) if self.xy_scale.text().strip() else 1
|
|
6528
|
+
z_scale = float(self.z_scale.text()) if self.z_scale.text().strip() else 1
|
|
6290
6529
|
|
|
6291
6530
|
if size is None:
|
|
6292
6531
|
return
|
|
@@ -6296,7 +6535,7 @@ class ComCellDialog(QDialog):
|
|
|
6296
6535
|
if my_network.node_centroids is None:
|
|
6297
6536
|
return
|
|
6298
6537
|
|
|
6299
|
-
my_network.community_cells(size = size)
|
|
6538
|
+
my_network.community_cells(size = size, xy_scale = xy_scale, z_scale = z_scale)
|
|
6300
6539
|
|
|
6301
6540
|
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID')
|
|
6302
6541
|
|
|
@@ -6577,6 +6816,60 @@ class RipleyDialog(QDialog):
|
|
|
6577
6816
|
print(traceback.format_exc())
|
|
6578
6817
|
print(f"Error: {e}")
|
|
6579
6818
|
|
|
6819
|
+
class HeatmapDialog(QDialog):
|
|
6820
|
+
|
|
6821
|
+
def __init__(self, parent = None):
|
|
6822
|
+
|
|
6823
|
+
super().__init__(parent)
|
|
6824
|
+
self.setWindowTitle("Heatmap Parameters")
|
|
6825
|
+
self.setModal(True)
|
|
6826
|
+
|
|
6827
|
+
layout = QFormLayout(self)
|
|
6828
|
+
|
|
6829
|
+
self.nodecount = QLineEdit("")
|
|
6830
|
+
layout.addRow("(Optional) Total Number of Nodes?:", self.nodecount)
|
|
6831
|
+
|
|
6832
|
+
|
|
6833
|
+
# stats checkbox (default True)
|
|
6834
|
+
self.is3d = QPushButton("3D")
|
|
6835
|
+
self.is3d.setCheckable(True)
|
|
6836
|
+
self.is3d.setChecked(True)
|
|
6837
|
+
layout.addRow("Use 3D Plot (uncheck for 2D)?:", self.is3d)
|
|
6838
|
+
|
|
6839
|
+
|
|
6840
|
+
# Add Run button
|
|
6841
|
+
run_button = QPushButton("Run")
|
|
6842
|
+
run_button.clicked.connect(self.run)
|
|
6843
|
+
layout.addWidget(run_button)
|
|
6844
|
+
|
|
6845
|
+
def run(self):
|
|
6846
|
+
|
|
6847
|
+
nodecount = int(self.nodecount.text()) if self.nodecount.text().strip() else None
|
|
6848
|
+
|
|
6849
|
+
is3d = self.is3d.isChecked()
|
|
6850
|
+
|
|
6851
|
+
|
|
6852
|
+
if my_network.communities is None:
|
|
6853
|
+
if my_network.network is not None:
|
|
6854
|
+
self.parent().show_partition_dialog()
|
|
6855
|
+
else:
|
|
6856
|
+
self.parent().handle_com_cell()
|
|
6857
|
+
if my_network.communities is None:
|
|
6858
|
+
return
|
|
6859
|
+
|
|
6860
|
+
heat_dict = my_network.community_heatmap(num_nodes = nodecount, is3d = is3d)
|
|
6861
|
+
|
|
6862
|
+
self.parent().format_for_upperright_table(heat_dict, metric='Community', value='ln(Predicted Community Nodecount/Actual)', title="Community Heatmap")
|
|
6863
|
+
|
|
6864
|
+
self.accept()
|
|
6865
|
+
|
|
6866
|
+
|
|
6867
|
+
|
|
6868
|
+
|
|
6869
|
+
|
|
6870
|
+
|
|
6871
|
+
|
|
6872
|
+
|
|
6580
6873
|
class RandomDialog(QDialog):
|
|
6581
6874
|
|
|
6582
6875
|
def __init__(self, parent=None):
|
|
@@ -10361,6 +10654,12 @@ class ModifyDialog(QDialog):
|
|
|
10361
10654
|
self.isolate.setChecked(False)
|
|
10362
10655
|
layout.addRow("Isolate connections between two specific node types (if assigned)?:", self.isolate)
|
|
10363
10656
|
|
|
10657
|
+
# isolate checkbox (default false)
|
|
10658
|
+
self.com_sizes = QPushButton("Communities By Size")
|
|
10659
|
+
self.com_sizes.setCheckable(True)
|
|
10660
|
+
self.com_sizes.setChecked(False)
|
|
10661
|
+
layout.addRow("Rearrange Community IDs by size?:", self.com_sizes)
|
|
10662
|
+
|
|
10364
10663
|
# Community collapse checkbox (default False)
|
|
10365
10664
|
self.comcollapse = QPushButton("Communities -> nodes")
|
|
10366
10665
|
self.comcollapse.setCheckable(True)
|
|
@@ -10403,6 +10702,7 @@ class ModifyDialog(QDialog):
|
|
|
10403
10702
|
isolate = self.isolate.isChecked()
|
|
10404
10703
|
comcollapse = self.comcollapse.isChecked()
|
|
10405
10704
|
remove = self.remove.isChecked()
|
|
10705
|
+
com_size = self.com_sizes.isChecked()
|
|
10406
10706
|
|
|
10407
10707
|
|
|
10408
10708
|
if isolate and my_network.node_identities is not None:
|
|
@@ -10453,6 +10753,14 @@ class ModifyDialog(QDialog):
|
|
|
10453
10753
|
self.parent().format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
10454
10754
|
except:
|
|
10455
10755
|
pass
|
|
10756
|
+
if com_size:
|
|
10757
|
+
if my_network.communities is None:
|
|
10758
|
+
self.parent().show_partition_dialog()
|
|
10759
|
+
if my_network.communities is None:
|
|
10760
|
+
return
|
|
10761
|
+
my_network.com_by_size()
|
|
10762
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'Community', 'Node Communities')
|
|
10763
|
+
|
|
10456
10764
|
if comcollapse:
|
|
10457
10765
|
if my_network.communities is None:
|
|
10458
10766
|
self.parent().show_partition_dialog()
|
|
@@ -10482,6 +10790,8 @@ class ModifyDialog(QDialog):
|
|
|
10482
10790
|
self.accept()
|
|
10483
10791
|
|
|
10484
10792
|
except Exception as e:
|
|
10793
|
+
import traceback
|
|
10794
|
+
print(traceback.format_exc())
|
|
10485
10795
|
print(f"An error occurred: {e}")
|
|
10486
10796
|
|
|
10487
10797
|
|
|
@@ -10612,85 +10922,107 @@ class CalcAllDialog(QDialog):
|
|
|
10612
10922
|
prev_fastdil = False
|
|
10613
10923
|
prev_overlays = False
|
|
10614
10924
|
prev_updates = True
|
|
10615
|
-
|
|
10925
|
+
|
|
10616
10926
|
def __init__(self, parent=None):
|
|
10617
10927
|
super().__init__(parent)
|
|
10618
|
-
self.setWindowTitle("Calculate
|
|
10928
|
+
self.setWindowTitle("Calculate Connectivity Network Parameters")
|
|
10619
10929
|
self.setModal(True)
|
|
10620
10930
|
|
|
10621
|
-
|
|
10931
|
+
# Main layout
|
|
10932
|
+
main_layout = QVBoxLayout(self)
|
|
10622
10933
|
|
|
10623
|
-
#
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
layout.addRow("Output Directory:", self.directory)
|
|
10934
|
+
# Important Parameters Group
|
|
10935
|
+
important_group = QGroupBox("Important Parameters")
|
|
10936
|
+
important_layout = QFormLayout(important_group)
|
|
10627
10937
|
|
|
10628
|
-
# Load previous values for all inputs
|
|
10629
10938
|
self.xy_scale = QLineEdit(f'{my_network.xy_scale}')
|
|
10630
|
-
|
|
10939
|
+
important_layout.addRow("xy_scale:", self.xy_scale)
|
|
10631
10940
|
|
|
10632
10941
|
self.z_scale = QLineEdit(f'{my_network.z_scale}')
|
|
10633
|
-
|
|
10634
|
-
|
|
10942
|
+
important_layout.addRow("z_scale:", self.z_scale)
|
|
10943
|
+
|
|
10635
10944
|
self.search = QLineEdit(self.prev_search)
|
|
10636
10945
|
self.search.setPlaceholderText("Leave empty for None")
|
|
10637
|
-
|
|
10638
|
-
|
|
10946
|
+
important_layout.addRow("Node Search (float):", self.search)
|
|
10947
|
+
|
|
10639
10948
|
self.diledge = QLineEdit(self.prev_diledge)
|
|
10640
10949
|
self.diledge.setPlaceholderText("Leave empty for None")
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
self.
|
|
10644
|
-
self.
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10950
|
+
important_layout.addRow("Edge Reconnection Distance (float):", self.diledge)
|
|
10951
|
+
|
|
10952
|
+
self.label_nodes = QPushButton("Label")
|
|
10953
|
+
self.label_nodes.setCheckable(True)
|
|
10954
|
+
self.label_nodes.setChecked(self.prev_label_nodes)
|
|
10955
|
+
important_layout.addRow("Re-Label Nodes (WARNING - OVERRIDES ANY CURRENT LABELS):", self.label_nodes)
|
|
10956
|
+
|
|
10957
|
+
main_layout.addWidget(important_group)
|
|
10958
|
+
|
|
10959
|
+
# Optional Parameters Group
|
|
10960
|
+
optional_group = QGroupBox("Optional Parameters")
|
|
10961
|
+
optional_layout = QFormLayout(optional_group)
|
|
10962
|
+
|
|
10651
10963
|
self.other_nodes = QLineEdit(self.prev_other_nodes)
|
|
10652
10964
|
self.other_nodes.setPlaceholderText("Leave empty for None")
|
|
10653
|
-
|
|
10654
|
-
|
|
10965
|
+
optional_layout.addRow("Filepath or directory containing additional node images:", self.other_nodes)
|
|
10966
|
+
|
|
10655
10967
|
self.remove_trunk = QLineEdit(self.prev_remove_trunk)
|
|
10656
10968
|
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
|
-
|
|
10969
|
+
optional_layout.addRow("Times to remove edge trunks (int):", self.remove_trunk)
|
|
10970
|
+
|
|
10670
10971
|
self.inners = QPushButton("Inner Edges")
|
|
10671
10972
|
self.inners.setCheckable(True)
|
|
10672
10973
|
self.inners.setChecked(self.prev_inners)
|
|
10673
|
-
|
|
10674
|
-
|
|
10974
|
+
optional_layout.addRow("Use Inner Edges:", self.inners)
|
|
10975
|
+
|
|
10976
|
+
main_layout.addWidget(optional_group)
|
|
10977
|
+
|
|
10978
|
+
# Speed Up Options Group
|
|
10979
|
+
speedup_group = QGroupBox("Speed Up Options")
|
|
10980
|
+
speedup_layout = QFormLayout(speedup_group)
|
|
10981
|
+
|
|
10982
|
+
self.down_factor = QLineEdit(self.prev_down_factor)
|
|
10983
|
+
self.down_factor.setPlaceholderText("Leave empty for None")
|
|
10984
|
+
speedup_layout.addRow("Downsample for Centroids (int):", self.down_factor)
|
|
10985
|
+
|
|
10986
|
+
self.GPU_downsample = QLineEdit(self.prev_GPU_downsample)
|
|
10987
|
+
self.GPU_downsample.setPlaceholderText("Leave empty for None")
|
|
10988
|
+
speedup_layout.addRow("Downsample for Distance Transform (GPU) (int):", self.GPU_downsample)
|
|
10989
|
+
|
|
10990
|
+
self.gpu = QPushButton("GPU")
|
|
10991
|
+
self.gpu.setCheckable(True)
|
|
10992
|
+
self.gpu.setChecked(self.prev_gpu)
|
|
10993
|
+
speedup_layout.addRow("Use GPU:", self.gpu)
|
|
10994
|
+
|
|
10675
10995
|
self.fastdil = QPushButton("Fast Dilate")
|
|
10676
10996
|
self.fastdil.setCheckable(True)
|
|
10677
10997
|
self.fastdil.setChecked(self.prev_fastdil)
|
|
10678
|
-
|
|
10679
|
-
|
|
10998
|
+
speedup_layout.addRow("Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
10999
|
+
|
|
11000
|
+
main_layout.addWidget(speedup_group)
|
|
11001
|
+
|
|
11002
|
+
# Output Options Group
|
|
11003
|
+
output_group = QGroupBox("Output Options")
|
|
11004
|
+
output_layout = QFormLayout(output_group)
|
|
11005
|
+
|
|
11006
|
+
self.directory = QLineEdit(self.prev_directory)
|
|
11007
|
+
self.directory.setPlaceholderText("Will Have to Save Manually If Empty")
|
|
11008
|
+
output_layout.addRow("Output Directory:", self.directory)
|
|
11009
|
+
|
|
10680
11010
|
self.overlays = QPushButton("Overlays")
|
|
10681
11011
|
self.overlays.setCheckable(True)
|
|
10682
11012
|
self.overlays.setChecked(self.prev_overlays)
|
|
10683
|
-
|
|
10684
|
-
|
|
11013
|
+
output_layout.addRow("Generate Overlays:", self.overlays)
|
|
11014
|
+
|
|
10685
11015
|
self.update = QPushButton("Update")
|
|
10686
11016
|
self.update.setCheckable(True)
|
|
10687
11017
|
self.update.setChecked(self.prev_updates)
|
|
10688
|
-
|
|
11018
|
+
output_layout.addRow("Update Node/Edge in NetTracer3D:", self.update)
|
|
11019
|
+
|
|
11020
|
+
main_layout.addWidget(output_group)
|
|
10689
11021
|
|
|
10690
11022
|
# Add Run button
|
|
10691
11023
|
run_button = QPushButton("Run Calculate All")
|
|
10692
11024
|
run_button.clicked.connect(self.run_calc_all)
|
|
10693
|
-
|
|
11025
|
+
main_layout.addWidget(run_button)
|
|
10694
11026
|
|
|
10695
11027
|
def run_calc_all(self):
|
|
10696
11028
|
|
|
@@ -10867,65 +11199,87 @@ class CalcAllDialog(QDialog):
|
|
|
10867
11199
|
f"Error running calculate all: {str(e)}"
|
|
10868
11200
|
)
|
|
10869
11201
|
|
|
10870
|
-
class ProxDialog(QDialog):
|
|
10871
11202
|
|
|
11203
|
+
class ProxDialog(QDialog):
|
|
10872
11204
|
def __init__(self, parent=None):
|
|
10873
11205
|
super().__init__(parent)
|
|
10874
11206
|
self.setWindowTitle("Calculate Proximity Network")
|
|
10875
11207
|
self.setModal(True)
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
11208
|
+
|
|
11209
|
+
# Main layout
|
|
11210
|
+
main_layout = QVBoxLayout(self)
|
|
11211
|
+
|
|
11212
|
+
# Important Parameters Group
|
|
11213
|
+
important_group = QGroupBox("Important Parameters")
|
|
11214
|
+
important_layout = QFormLayout(important_group)
|
|
11215
|
+
|
|
10884
11216
|
self.search = QLineEdit()
|
|
10885
11217
|
self.search.setPlaceholderText("search")
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
# Load previous values for all inputs
|
|
11218
|
+
important_layout.addRow("Search Region Distance? (enter true value corresponding to scaling, ie in microns):", self.search)
|
|
11219
|
+
|
|
10889
11220
|
self.xy_scale = QLineEdit(f"{my_network.xy_scale}")
|
|
10890
|
-
|
|
11221
|
+
important_layout.addRow("xy_scale:", self.xy_scale)
|
|
10891
11222
|
|
|
10892
11223
|
self.z_scale = QLineEdit(f"{my_network.z_scale}")
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
11224
|
+
important_layout.addRow("z_scale:", self.z_scale)
|
|
11225
|
+
|
|
11226
|
+
main_layout.addWidget(important_group)
|
|
11227
|
+
|
|
11228
|
+
# Mode Group
|
|
11229
|
+
mode_group = QGroupBox("Mode")
|
|
11230
|
+
mode_layout = QFormLayout(mode_group)
|
|
11231
|
+
|
|
10896
11232
|
self.mode_selector = QComboBox()
|
|
10897
11233
|
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
11234
|
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
|
-
|
|
11235
|
+
mode_layout.addRow("Execution Mode:", self.mode_selector)
|
|
11236
|
+
|
|
10906
11237
|
if my_network.node_identities is not None:
|
|
10907
11238
|
self.id_selector = QComboBox()
|
|
10908
11239
|
# Add all options from id dictionary
|
|
10909
11240
|
self.id_selector.addItems(['None'] + list(set(my_network.node_identities.values())))
|
|
10910
11241
|
self.id_selector.setCurrentIndex(0) # Default to Mode 1
|
|
10911
|
-
|
|
11242
|
+
mode_layout.addRow("Create Networks only from a specific node identity?:", self.id_selector)
|
|
10912
11243
|
else:
|
|
10913
11244
|
self.id_selector = None
|
|
10914
|
-
|
|
11245
|
+
|
|
11246
|
+
main_layout.addWidget(mode_group)
|
|
11247
|
+
|
|
11248
|
+
# Output Options Group
|
|
11249
|
+
output_group = QGroupBox("Output Options")
|
|
11250
|
+
output_layout = QFormLayout(output_group)
|
|
11251
|
+
|
|
11252
|
+
self.directory = QLineEdit('')
|
|
11253
|
+
self.directory.setPlaceholderText("Leave empty for 'my_network'")
|
|
11254
|
+
output_layout.addRow("Output Directory:", self.directory)
|
|
11255
|
+
|
|
10915
11256
|
self.overlays = QPushButton("Overlays")
|
|
10916
11257
|
self.overlays.setCheckable(True)
|
|
10917
11258
|
self.overlays.setChecked(True)
|
|
10918
|
-
|
|
10919
|
-
|
|
11259
|
+
output_layout.addRow("Generate Overlays:", self.overlays)
|
|
11260
|
+
|
|
10920
11261
|
self.populate = QPushButton("Populate Nodes from Centroids?")
|
|
10921
11262
|
self.populate.setCheckable(True)
|
|
10922
11263
|
self.populate.setChecked(False)
|
|
10923
|
-
|
|
10924
|
-
|
|
11264
|
+
output_layout.addRow("If using centroid search:", self.populate)
|
|
11265
|
+
|
|
11266
|
+
main_layout.addWidget(output_group)
|
|
11267
|
+
|
|
11268
|
+
# Speed Up Options Group
|
|
11269
|
+
speedup_group = QGroupBox("Speed Up Options")
|
|
11270
|
+
speedup_layout = QFormLayout(speedup_group)
|
|
11271
|
+
|
|
11272
|
+
self.fastdil = QPushButton("Fast Dilate")
|
|
11273
|
+
self.fastdil.setCheckable(True)
|
|
11274
|
+
self.fastdil.setChecked(False)
|
|
11275
|
+
speedup_layout.addRow("(If using morphological) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
11276
|
+
|
|
11277
|
+
main_layout.addWidget(speedup_group)
|
|
11278
|
+
|
|
10925
11279
|
# Add Run button
|
|
10926
11280
|
run_button = QPushButton("Run Proximity Network")
|
|
10927
11281
|
run_button.clicked.connect(self.prox)
|
|
10928
|
-
|
|
11282
|
+
main_layout.addWidget(run_button)
|
|
10929
11283
|
|
|
10930
11284
|
def prox(self):
|
|
10931
11285
|
|
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.8
|
|
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,14 @@ 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.7 Updates --
|
|
77
|
-
|
|
78
|
-
*
|
|
76
|
+
-- Version 0.7.8 (and 0.7.7) Updates --
|
|
77
|
+
|
|
78
|
+
* Bug Fixes
|
|
79
|
+
* Added the excel helper loader for better automated loading from QuPath exports specifically
|
|
80
|
+
* Added the ability to cluster communities into broader neighborhoods (with KMeans) based on their compositions.
|
|
81
|
+
* Added heatmap and UMAP graph displays based on community compositions.
|
|
82
|
+
* Added the ability to show heatmaps of nodes based on their density within their communities
|
|
83
|
+
* Added the ability to cluster nodes into communities based on spatial grouping in arbitrarily-sized cells (rather than just using the network)
|
|
84
|
+
* Added function to crop the current image
|
|
85
|
+
* More options under 'Modify Network'
|
|
86
|
+
* 'Show 3D' method now can render a bounding box.
|
|
@@ -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=WTcN-tOk4vzDLhnVN4un5PEkC90GDp3sxZZhgLifMOM,468787
|
|
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.8.dist-info/licenses/LICENSE,sha256=gM207DhJjWrxLuEWXl0Qz5ISbtWDmADfjHp3yC2XISs,888
|
|
19
|
+
nettracer3d-0.7.8.dist-info/METADATA,sha256=GFylr691RVFXgRMrrEhOEW6ySL5vUW-8-bK8qQ4hr9A,4797
|
|
20
|
+
nettracer3d-0.7.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
nettracer3d-0.7.8.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
|
|
22
|
+
nettracer3d-0.7.8.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
|
|
23
|
+
nettracer3d-0.7.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|