nettracer3d 0.9.2__py3-none-any.whl → 0.9.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nettracer3d/excelotron.py +73 -19
- nettracer3d/neighborhoods.py +213 -17
- nettracer3d/nettracer.py +149 -16
- nettracer3d/nettracer_gui.py +37 -5
- {nettracer3d-0.9.2.dist-info → nettracer3d-0.9.4.dist-info}/METADATA +4 -6
- {nettracer3d-0.9.2.dist-info → nettracer3d-0.9.4.dist-info}/RECORD +10 -10
- {nettracer3d-0.9.2.dist-info → nettracer3d-0.9.4.dist-info}/WHEEL +0 -0
- {nettracer3d-0.9.2.dist-info → nettracer3d-0.9.4.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.9.2.dist-info → nettracer3d-0.9.4.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.9.2.dist-info → nettracer3d-0.9.4.dist-info}/top_level.txt +0 -0
nettracer3d/excelotron.py
CHANGED
|
@@ -4,7 +4,7 @@ import numpy as np
|
|
|
4
4
|
from PyQt6.QtWidgets import (QApplication, QMainWindow, QHBoxLayout, QVBoxLayout,
|
|
5
5
|
QWidget, QTableWidget, QTableWidgetItem, QPushButton,
|
|
6
6
|
QLabel, QLineEdit, QScrollArea, QFrame, QMessageBox,
|
|
7
|
-
QHeaderView, QAbstractItemView, QSplitter, QTabWidget)
|
|
7
|
+
QHeaderView, QAbstractItemView, QSplitter, QTabWidget, QCheckBox)
|
|
8
8
|
from PyQt6.QtCore import Qt, QMimeData, pyqtSignal
|
|
9
9
|
from PyQt6.QtGui import QDragEnterEvent, QDropEvent, QDrag, QPainter, QPixmap
|
|
10
10
|
import os
|
|
@@ -502,6 +502,35 @@ class ClassifierGroupWidget(QFrame):
|
|
|
502
502
|
|
|
503
503
|
header_layout.addStretch()
|
|
504
504
|
|
|
505
|
+
# Hierarchical toggle
|
|
506
|
+
self.hierarchical_checkbox = QCheckBox("Hierarchical")
|
|
507
|
+
self.hierarchical_checkbox.setChecked(True) # Default to hierarchical
|
|
508
|
+
self.hierarchical_checkbox.setStyleSheet("""
|
|
509
|
+
QCheckBox {
|
|
510
|
+
font-weight: bold;
|
|
511
|
+
color: #007acc;
|
|
512
|
+
padding: 5px;
|
|
513
|
+
}
|
|
514
|
+
QCheckBox::indicator {
|
|
515
|
+
width: 18px;
|
|
516
|
+
height: 18px;
|
|
517
|
+
}
|
|
518
|
+
QCheckBox::indicator:unchecked {
|
|
519
|
+
border: 2px solid #007acc;
|
|
520
|
+
background-color: white;
|
|
521
|
+
border-radius: 3px;
|
|
522
|
+
}
|
|
523
|
+
QCheckBox::indicator:checked {
|
|
524
|
+
border: 2px solid #007acc;
|
|
525
|
+
background-color: #007acc;
|
|
526
|
+
border-radius: 3px;
|
|
527
|
+
}
|
|
528
|
+
QCheckBox::indicator:checked:hover {
|
|
529
|
+
background-color: #005a9e;
|
|
530
|
+
}
|
|
531
|
+
""")
|
|
532
|
+
header_layout.addWidget(self.hierarchical_checkbox)
|
|
533
|
+
|
|
505
534
|
# Add classifier button
|
|
506
535
|
add_btn = QPushButton("+ Add Classifier")
|
|
507
536
|
add_btn.setStyleSheet("""
|
|
@@ -559,7 +588,7 @@ class ClassifierGroupWidget(QFrame):
|
|
|
559
588
|
layout.addWidget(preview_btn)
|
|
560
589
|
|
|
561
590
|
self.setLayout(layout)
|
|
562
|
-
|
|
591
|
+
|
|
563
592
|
def add_classifier(self):
|
|
564
593
|
self.classifier_counter += 1
|
|
565
594
|
classifier_id = self.classifier_counter
|
|
@@ -597,25 +626,50 @@ class ClassifierGroupWidget(QFrame):
|
|
|
597
626
|
matched_identities = set()
|
|
598
627
|
classifier_usage = {classifier_id: 0 for classifier_id in classifier_ids} # Now classifier_ids is defined
|
|
599
628
|
|
|
629
|
+
|
|
600
630
|
for identity in original_identities:
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
# Check classifiers in order
|
|
604
|
-
for classifier_id in classifier_ids:
|
|
605
|
-
classifier = self.classifiers[classifier_id]
|
|
606
|
-
|
|
607
|
-
if classifier.matches_identity(identity_str):
|
|
608
|
-
# This classifier matches
|
|
609
|
-
matched_identities.add(identity)
|
|
610
|
-
classifier_usage[classifier_id] += 1 # Track usage
|
|
631
|
+
identity_str = str(identity)
|
|
611
632
|
|
|
612
|
-
#
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
633
|
+
# Check classifiers in order
|
|
634
|
+
for classifier_id in classifier_ids:
|
|
635
|
+
classifier = self.classifiers[classifier_id]
|
|
636
|
+
|
|
637
|
+
if classifier.matches_identity(identity_str):
|
|
638
|
+
# This classifier matches
|
|
639
|
+
matched_identities.add(identity)
|
|
640
|
+
classifier_usage[classifier_id] += 1 # Track usage
|
|
641
|
+
|
|
642
|
+
# Set the new ID if provided
|
|
643
|
+
new_id = classifier.get_new_id()
|
|
644
|
+
if new_id and identity in self.identity_remap_widget.identity_mappings:
|
|
645
|
+
if self.hierarchical_checkbox.isChecked():
|
|
646
|
+
# Hierarchical mode - just set the new ID
|
|
647
|
+
self.identity_remap_widget.identity_mappings[identity]['new_edit'].setText(new_id)
|
|
648
|
+
else:
|
|
649
|
+
# Non-hierarchical mode - append to existing or create new
|
|
650
|
+
current_text = self.identity_remap_widget.identity_mappings[identity]['new_edit'].text().strip()
|
|
651
|
+
if current_text:
|
|
652
|
+
# Parse existing text to see if it's already a list
|
|
653
|
+
try:
|
|
654
|
+
existing_list = literal_eval(current_text)
|
|
655
|
+
if isinstance(existing_list, list):
|
|
656
|
+
existing_list.append(new_id)
|
|
657
|
+
new_text = str(existing_list)
|
|
658
|
+
else:
|
|
659
|
+
# Current text is a single value, make it a list
|
|
660
|
+
new_text = str([current_text, new_id])
|
|
661
|
+
except:
|
|
662
|
+
# If parsing fails, treat as single value
|
|
663
|
+
new_text = str([current_text, new_id])
|
|
664
|
+
else:
|
|
665
|
+
# No existing text, just set the new ID
|
|
666
|
+
new_text = new_id
|
|
667
|
+
|
|
668
|
+
self.identity_remap_widget.identity_mappings[identity]['new_edit'].setText(new_text)
|
|
669
|
+
|
|
670
|
+
# Only break if hierarchical mode (first match wins)
|
|
671
|
+
if self.hierarchical_checkbox.isChecked():
|
|
672
|
+
break
|
|
619
673
|
|
|
620
674
|
# Remove identities that didn't match any classifier
|
|
621
675
|
unmatched_identities = set(original_identities) - matched_identities
|
nettracer3d/neighborhoods.py
CHANGED
|
@@ -347,7 +347,8 @@ def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
|
|
|
347
347
|
id_dictionary: Optional[Dict[int, str]] = None,
|
|
348
348
|
graph_label = "Community ID",
|
|
349
349
|
title = 'UMAP Visualization of Community Compositions',
|
|
350
|
-
neighborhoods: Optional[Dict[int, int]] = None
|
|
350
|
+
neighborhoods: Optional[Dict[int, int]] = None,
|
|
351
|
+
draw_lines: bool = False):
|
|
351
352
|
"""
|
|
352
353
|
Convert cluster composition data to UMAP visualization.
|
|
353
354
|
|
|
@@ -370,6 +371,8 @@ def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
|
|
|
370
371
|
neighborhoods : dict, optional
|
|
371
372
|
Dictionary mapping node IDs to neighborhood IDs {node_id: neighborhood_id}.
|
|
372
373
|
If provided, points will be colored by neighborhood using community coloration methods.
|
|
374
|
+
draw_lines : bool
|
|
375
|
+
Whether to draw lines between nodes that share identities (default: False)
|
|
373
376
|
|
|
374
377
|
Returns:
|
|
375
378
|
--------
|
|
@@ -453,15 +456,111 @@ def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
|
|
|
453
456
|
plt.figure(figsize=(12, 8))
|
|
454
457
|
|
|
455
458
|
if n_components == 2:
|
|
456
|
-
if
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
459
|
+
# Draw scatter with different markers for multi-identity nodes if draw_lines is enabled
|
|
460
|
+
if draw_lines:
|
|
461
|
+
# Separate multi-identity and singleton nodes for different markers
|
|
462
|
+
singleton_indices = []
|
|
463
|
+
multi_indices = []
|
|
464
|
+
singleton_colors = []
|
|
465
|
+
multi_colors = []
|
|
466
|
+
|
|
467
|
+
for i, cluster_id in enumerate(cluster_ids):
|
|
468
|
+
vec = cluster_data[cluster_id]
|
|
469
|
+
if np.sum(vec) > 1: # Multi-identity
|
|
470
|
+
multi_indices.append(i)
|
|
471
|
+
multi_colors.append(point_colors[i] if isinstance(point_colors, list) else point_colors)
|
|
472
|
+
else: # Singleton
|
|
473
|
+
singleton_indices.append(i)
|
|
474
|
+
singleton_colors.append(point_colors[i] if isinstance(point_colors, list) else point_colors)
|
|
475
|
+
|
|
476
|
+
# Draw singleton nodes as circles
|
|
477
|
+
if singleton_indices:
|
|
478
|
+
if use_neighborhood_coloring or use_identity_coloring:
|
|
479
|
+
scatter1 = plt.scatter(embedding[singleton_indices, 0], embedding[singleton_indices, 1],
|
|
480
|
+
c=singleton_colors, s=100, alpha=0.7, marker='o')
|
|
481
|
+
else:
|
|
482
|
+
scatter1 = plt.scatter(embedding[singleton_indices, 0], embedding[singleton_indices, 1],
|
|
483
|
+
c=[point_colors[i] for i in singleton_indices], cmap='viridis', s=100, alpha=0.7, marker='o')
|
|
484
|
+
|
|
485
|
+
# Draw multi-identity nodes as squares
|
|
486
|
+
if multi_indices:
|
|
487
|
+
if use_neighborhood_coloring or use_identity_coloring:
|
|
488
|
+
scatter2 = plt.scatter(embedding[multi_indices, 0], embedding[multi_indices, 1],
|
|
489
|
+
c=multi_colors, s=100, alpha=0.7, marker='s')
|
|
490
|
+
else:
|
|
491
|
+
scatter2 = plt.scatter(embedding[multi_indices, 0], embedding[multi_indices, 1],
|
|
492
|
+
c=[point_colors[i] for i in multi_indices], cmap='viridis', s=100, alpha=0.7, marker='s')
|
|
493
|
+
scatter = scatter2 # For colorbar reference
|
|
494
|
+
else:
|
|
495
|
+
scatter = scatter1 if singleton_indices else None
|
|
462
496
|
else:
|
|
463
|
-
|
|
464
|
-
|
|
497
|
+
# Original behavior when draw_lines is False
|
|
498
|
+
if use_neighborhood_coloring:
|
|
499
|
+
scatter = plt.scatter(embedding[:, 0], embedding[:, 1],
|
|
500
|
+
c=point_colors, s=100, alpha=0.7)
|
|
501
|
+
elif use_identity_coloring:
|
|
502
|
+
scatter = plt.scatter(embedding[:, 0], embedding[:, 1],
|
|
503
|
+
c=point_colors, s=100, alpha=0.7)
|
|
504
|
+
else:
|
|
505
|
+
scatter = plt.scatter(embedding[:, 0], embedding[:, 1],
|
|
506
|
+
c=point_colors, cmap='viridis', s=100, alpha=0.7)
|
|
507
|
+
|
|
508
|
+
# Draw lines between nodes with shared identities (only if draw_lines=True)
|
|
509
|
+
if draw_lines:
|
|
510
|
+
# First pass: identify unique multi-identity configurations and their representatives
|
|
511
|
+
multi_config_map = {} # Maps tuple(config) -> {'count': int, 'representative_idx': int}
|
|
512
|
+
|
|
513
|
+
for i, cluster_id in enumerate(cluster_ids):
|
|
514
|
+
vec = cluster_data[cluster_id]
|
|
515
|
+
if np.sum(vec) > 1: # Multi-identity node
|
|
516
|
+
config = tuple(vec) # Convert to hashable tuple
|
|
517
|
+
if config not in multi_config_map:
|
|
518
|
+
multi_config_map[config] = {'count': 1, 'representative_idx': i}
|
|
519
|
+
else:
|
|
520
|
+
multi_config_map[config]['count'] += 1
|
|
521
|
+
|
|
522
|
+
# Second pass: draw lines for each unique configuration
|
|
523
|
+
for config, info in multi_config_map.items():
|
|
524
|
+
i = info['representative_idx']
|
|
525
|
+
count = info['count']
|
|
526
|
+
vec1 = np.array(config)
|
|
527
|
+
|
|
528
|
+
# For each identity this configuration has, find the closest representative
|
|
529
|
+
identity_indices = np.where(vec1 == 1)[0]
|
|
530
|
+
|
|
531
|
+
for identity_idx in identity_indices:
|
|
532
|
+
best_target = None
|
|
533
|
+
best_distance = float('inf')
|
|
534
|
+
backup_target = None
|
|
535
|
+
backup_distance = float('inf')
|
|
536
|
+
|
|
537
|
+
# Find closest node with this specific identity
|
|
538
|
+
for j, cluster_id2 in enumerate(cluster_ids):
|
|
539
|
+
if i != j: # Don't connect to self
|
|
540
|
+
vec2 = cluster_data[cluster_id2]
|
|
541
|
+
if vec2[identity_idx] == 1: # Shares this specific identity
|
|
542
|
+
distance = np.linalg.norm(embedding[i] - embedding[j])
|
|
543
|
+
|
|
544
|
+
# Prefer singleton nodes
|
|
545
|
+
if np.sum(vec2) == 1: # Singleton
|
|
546
|
+
if distance < best_distance:
|
|
547
|
+
best_distance = distance
|
|
548
|
+
best_target = j
|
|
549
|
+
else: # Multi-identity node (backup)
|
|
550
|
+
if distance < backup_distance:
|
|
551
|
+
backup_distance = distance
|
|
552
|
+
backup_target = j
|
|
553
|
+
|
|
554
|
+
# Draw line to best target (prefer singleton, fallback to multi)
|
|
555
|
+
target = best_target if best_target is not None else backup_target
|
|
556
|
+
if target is not None:
|
|
557
|
+
# Calculate relative line weight with reasonable cap
|
|
558
|
+
max_count = max(info['count'] for info in multi_config_map.values())
|
|
559
|
+
relative_weight = count / max_count # Normalize to 0-1
|
|
560
|
+
line_weight = 0.3 + relative_weight * 1.2 # Scale to 0.3-1.5 range
|
|
561
|
+
plt.plot([embedding[i, 0], embedding[target, 0]],
|
|
562
|
+
[embedding[i, 1], embedding[target, 1]],
|
|
563
|
+
alpha=0.3, color='gray', linewidth=line_weight)
|
|
465
564
|
|
|
466
565
|
if label:
|
|
467
566
|
# Add cluster ID labels
|
|
@@ -516,15 +615,112 @@ def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
|
|
|
516
615
|
fig = plt.figure(figsize=(14, 10))
|
|
517
616
|
ax = fig.add_subplot(111, projection='3d')
|
|
518
617
|
|
|
519
|
-
if
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
618
|
+
# Draw scatter with different markers for multi-identity nodes if draw_lines is enabled
|
|
619
|
+
if draw_lines:
|
|
620
|
+
# Separate multi-identity and singleton nodes for different markers
|
|
621
|
+
singleton_indices = []
|
|
622
|
+
multi_indices = []
|
|
623
|
+
singleton_colors = []
|
|
624
|
+
multi_colors = []
|
|
625
|
+
|
|
626
|
+
for i, cluster_id in enumerate(cluster_ids):
|
|
627
|
+
vec = cluster_data[cluster_id]
|
|
628
|
+
if np.sum(vec) > 1: # Multi-identity
|
|
629
|
+
multi_indices.append(i)
|
|
630
|
+
multi_colors.append(point_colors[i] if isinstance(point_colors, list) else point_colors)
|
|
631
|
+
else: # Singleton
|
|
632
|
+
singleton_indices.append(i)
|
|
633
|
+
singleton_colors.append(point_colors[i] if isinstance(point_colors, list) else point_colors)
|
|
634
|
+
|
|
635
|
+
# Draw singleton nodes as circles
|
|
636
|
+
if singleton_indices:
|
|
637
|
+
if use_neighborhood_coloring or use_identity_coloring:
|
|
638
|
+
scatter1 = ax.scatter(embedding[singleton_indices, 0], embedding[singleton_indices, 1], embedding[singleton_indices, 2],
|
|
639
|
+
c=singleton_colors, s=100, alpha=0.7, marker='o')
|
|
640
|
+
else:
|
|
641
|
+
scatter1 = ax.scatter(embedding[singleton_indices, 0], embedding[singleton_indices, 1], embedding[singleton_indices, 2],
|
|
642
|
+
c=[point_colors[i] for i in singleton_indices], cmap='viridis', s=100, alpha=0.7, marker='o')
|
|
643
|
+
|
|
644
|
+
# Draw multi-identity nodes as squares
|
|
645
|
+
if multi_indices:
|
|
646
|
+
if use_neighborhood_coloring or use_identity_coloring:
|
|
647
|
+
scatter2 = ax.scatter(embedding[multi_indices, 0], embedding[multi_indices, 1], embedding[multi_indices, 2],
|
|
648
|
+
c=multi_colors, s=100, alpha=0.7, marker='s')
|
|
649
|
+
else:
|
|
650
|
+
scatter2 = ax.scatter(embedding[multi_indices, 0], embedding[multi_indices, 1], embedding[multi_indices, 2],
|
|
651
|
+
c=[point_colors[i] for i in multi_indices], cmap='viridis', s=100, alpha=0.7, marker='s')
|
|
652
|
+
scatter = scatter2 # For colorbar reference
|
|
653
|
+
else:
|
|
654
|
+
scatter = scatter1 if singleton_indices else None
|
|
525
655
|
else:
|
|
526
|
-
|
|
527
|
-
|
|
656
|
+
# Original behavior when draw_lines is False
|
|
657
|
+
if use_neighborhood_coloring:
|
|
658
|
+
scatter = ax.scatter(embedding[:, 0], embedding[:, 1], embedding[:, 2],
|
|
659
|
+
c=point_colors, s=100, alpha=0.7)
|
|
660
|
+
elif use_identity_coloring:
|
|
661
|
+
scatter = ax.scatter(embedding[:, 0], embedding[:, 1], embedding[:, 2],
|
|
662
|
+
c=point_colors, s=100, alpha=0.7)
|
|
663
|
+
else:
|
|
664
|
+
scatter = ax.scatter(embedding[:, 0], embedding[:, 1], embedding[:, 2],
|
|
665
|
+
c=point_colors, cmap='viridis', s=100, alpha=0.7)
|
|
666
|
+
|
|
667
|
+
# Draw lines between nodes with shared identities (only if draw_lines=True)
|
|
668
|
+
if draw_lines:
|
|
669
|
+
# First pass: identify unique multi-identity configurations and their representatives
|
|
670
|
+
multi_config_map = {} # Maps tuple(config) -> {'count': int, 'representative_idx': int}
|
|
671
|
+
|
|
672
|
+
for i, cluster_id in enumerate(cluster_ids):
|
|
673
|
+
vec = cluster_data[cluster_id]
|
|
674
|
+
if np.sum(vec) > 1: # Multi-identity node
|
|
675
|
+
config = tuple(vec) # Convert to hashable tuple
|
|
676
|
+
if config not in multi_config_map:
|
|
677
|
+
multi_config_map[config] = {'count': 1, 'representative_idx': i}
|
|
678
|
+
else:
|
|
679
|
+
multi_config_map[config]['count'] += 1
|
|
680
|
+
|
|
681
|
+
# Second pass: draw lines for each unique configuration
|
|
682
|
+
for config, info in multi_config_map.items():
|
|
683
|
+
i = info['representative_idx']
|
|
684
|
+
count = info['count']
|
|
685
|
+
vec1 = np.array(config)
|
|
686
|
+
|
|
687
|
+
# For each identity this configuration has, find the closest representative
|
|
688
|
+
identity_indices = np.where(vec1 == 1)[0]
|
|
689
|
+
|
|
690
|
+
for identity_idx in identity_indices:
|
|
691
|
+
best_target = None
|
|
692
|
+
best_distance = float('inf')
|
|
693
|
+
backup_target = None
|
|
694
|
+
backup_distance = float('inf')
|
|
695
|
+
|
|
696
|
+
# Find closest node with this specific identity
|
|
697
|
+
for j, cluster_id2 in enumerate(cluster_ids):
|
|
698
|
+
if i != j: # Don't connect to self
|
|
699
|
+
vec2 = cluster_data[cluster_id2]
|
|
700
|
+
if vec2[identity_idx] == 1: # Shares this specific identity
|
|
701
|
+
distance = np.linalg.norm(embedding[i] - embedding[j])
|
|
702
|
+
|
|
703
|
+
# Prefer singleton nodes
|
|
704
|
+
if np.sum(vec2) == 1: # Singleton
|
|
705
|
+
if distance < best_distance:
|
|
706
|
+
best_distance = distance
|
|
707
|
+
best_target = j
|
|
708
|
+
else: # Multi-identity node (backup)
|
|
709
|
+
if distance < backup_distance:
|
|
710
|
+
backup_distance = distance
|
|
711
|
+
backup_target = j
|
|
712
|
+
|
|
713
|
+
# Draw line to best target (prefer singleton, fallback to multi)
|
|
714
|
+
target = best_target if best_target is not None else backup_target
|
|
715
|
+
if target is not None:
|
|
716
|
+
# Calculate relative line weight with reasonable cap
|
|
717
|
+
max_count = max(info['count'] for info in multi_config_map.values())
|
|
718
|
+
relative_weight = count / max_count # Normalize to 0-1
|
|
719
|
+
line_weight = 0.3 + relative_weight * 1.2 # Scale to 0.3-1.5 range
|
|
720
|
+
ax.plot([embedding[i, 0], embedding[target, 0]],
|
|
721
|
+
[embedding[i, 1], embedding[target, 1]],
|
|
722
|
+
[embedding[i, 2], embedding[target, 2]],
|
|
723
|
+
alpha=0.3, color='gray', linewidth=line_weight)
|
|
528
724
|
|
|
529
725
|
if label:
|
|
530
726
|
# Add cluster ID labels
|
nettracer3d/nettracer.py
CHANGED
|
@@ -4,6 +4,7 @@ import tifffile
|
|
|
4
4
|
from scipy import ndimage
|
|
5
5
|
from skimage import measure
|
|
6
6
|
import cv2
|
|
7
|
+
import ast
|
|
7
8
|
import concurrent.futures
|
|
8
9
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
9
10
|
from scipy.ndimage import zoom
|
|
@@ -382,6 +383,27 @@ def invert_dict(d):
|
|
|
382
383
|
inverted.setdefault(value, []).append(key)
|
|
383
384
|
return inverted
|
|
384
385
|
|
|
386
|
+
def invert_dict_special(d):
|
|
387
|
+
|
|
388
|
+
d = invert_dict(d)
|
|
389
|
+
|
|
390
|
+
new_dict = copy.deepcopy(d)
|
|
391
|
+
|
|
392
|
+
for key, vals in d.items():
|
|
393
|
+
|
|
394
|
+
try:
|
|
395
|
+
idens = ast.literal_eval(key)
|
|
396
|
+
for iden in idens:
|
|
397
|
+
try:
|
|
398
|
+
new_dict[iden].extend(vals)
|
|
399
|
+
except:
|
|
400
|
+
new_dict[iden] = vals
|
|
401
|
+
del new_dict[key]
|
|
402
|
+
except:
|
|
403
|
+
pass
|
|
404
|
+
return new_dict
|
|
405
|
+
|
|
406
|
+
|
|
385
407
|
def invert_array(array):
|
|
386
408
|
"""Internal method used to flip node array indices. 0 becomes 255 and vice versa."""
|
|
387
409
|
inverted_array = np.where(array == 0, 255, 0).astype(np.uint8)
|
|
@@ -2117,7 +2139,19 @@ def erode(arrayimage, amount, xy_scale = 1, z_scale = 1, mode = 0):
|
|
|
2117
2139
|
|
|
2118
2140
|
return arrayimage
|
|
2119
2141
|
|
|
2142
|
+
def iden_set(idens):
|
|
2143
|
+
|
|
2144
|
+
idens = set(idens)
|
|
2145
|
+
real_iden_set = []
|
|
2146
|
+
for iden in idens:
|
|
2147
|
+
try:
|
|
2148
|
+
options = ast.literal_eval(iden)
|
|
2149
|
+
for opt in options:
|
|
2150
|
+
real_iden_set.append(opt)
|
|
2151
|
+
except:
|
|
2152
|
+
real_iden_set.append(iden)
|
|
2120
2153
|
|
|
2154
|
+
return set(real_iden_set)
|
|
2121
2155
|
|
|
2122
2156
|
|
|
2123
2157
|
|
|
@@ -5451,7 +5485,7 @@ class Network_3D:
|
|
|
5451
5485
|
|
|
5452
5486
|
community_dict = invert_dict(self.communities)
|
|
5453
5487
|
summation = 0
|
|
5454
|
-
id_set =
|
|
5488
|
+
id_set = iden_set(self.node_identities.values())
|
|
5455
5489
|
output = {sort: 0 for sort in id_set}
|
|
5456
5490
|
template = copy.deepcopy(output)
|
|
5457
5491
|
|
|
@@ -5463,7 +5497,12 @@ class Network_3D:
|
|
|
5463
5497
|
|
|
5464
5498
|
# Count identities in this community
|
|
5465
5499
|
for node in nodes:
|
|
5466
|
-
|
|
5500
|
+
try:
|
|
5501
|
+
idens = ast.literal_eval(self.node_identities[node])
|
|
5502
|
+
for iden in idens:
|
|
5503
|
+
counter[iden] += 1
|
|
5504
|
+
except:
|
|
5505
|
+
counter[self.node_identities[node]] += 1
|
|
5467
5506
|
|
|
5468
5507
|
# Convert to proportions within this community and weight by size
|
|
5469
5508
|
for sort in counter:
|
|
@@ -5492,11 +5531,60 @@ class Network_3D:
|
|
|
5492
5531
|
neighborhoods.visualize_cluster_composition_umap(self.node_centroids, None, id_dictionary = self.node_identities, graph_label = "Node ID", title = 'UMAP Visualization of Node Centroids')
|
|
5493
5532
|
|
|
5494
5533
|
|
|
5534
|
+
|
|
5535
|
+
def identity_umap(self):
|
|
5536
|
+
|
|
5537
|
+
try:
|
|
5538
|
+
|
|
5539
|
+
id_set = iden_set(self.node_identities.values())
|
|
5540
|
+
|
|
5541
|
+
template = np.zeros(len(id_set))
|
|
5542
|
+
|
|
5543
|
+
id_dict = {}
|
|
5544
|
+
for i, iden in enumerate(id_set):
|
|
5545
|
+
id_dict[iden] = i
|
|
5546
|
+
|
|
5547
|
+
umap_dict = {}
|
|
5548
|
+
|
|
5549
|
+
for node in self.node_identities.keys():
|
|
5550
|
+
umap_dict[node] = copy.deepcopy(template)
|
|
5551
|
+
try:
|
|
5552
|
+
idens = ast.literal_eval(self.node_identities[node])
|
|
5553
|
+
for iden in idens:
|
|
5554
|
+
index = id_dict[iden]
|
|
5555
|
+
ref = umap_dict[node]
|
|
5556
|
+
ref[index] = 1
|
|
5557
|
+
umap_dict[node] = ref
|
|
5558
|
+
except:
|
|
5559
|
+
index = id_dict[self.node_identities[node]]
|
|
5560
|
+
ref = umap_dict[node]
|
|
5561
|
+
ref[index] = 1
|
|
5562
|
+
umap_dict[node] = ref
|
|
5563
|
+
|
|
5564
|
+
neighbor_classes = {}
|
|
5565
|
+
import random
|
|
5566
|
+
|
|
5567
|
+
for node, iden in self.node_identities.items():
|
|
5568
|
+
try:
|
|
5569
|
+
idens = ast.literal_eval(iden)
|
|
5570
|
+
neighbor_classes[node] = random.choice(idens)
|
|
5571
|
+
except:
|
|
5572
|
+
neighbor_classes[node] = iden
|
|
5573
|
+
|
|
5574
|
+
|
|
5575
|
+
from . import neighborhoods
|
|
5576
|
+
|
|
5577
|
+
neighborhoods.visualize_cluster_composition_umap(umap_dict, None, id_dictionary = neighbor_classes, graph_label = "Node ID", title = 'UMAP Visualization of Node Identities', draw_lines = True)
|
|
5578
|
+
|
|
5579
|
+
except Exception as e:
|
|
5580
|
+
print(f"Error: {e}")
|
|
5581
|
+
|
|
5582
|
+
|
|
5495
5583
|
def community_id_info_per_com(self, umap = False, label = 0, limit = 0, proportional = False, neighbors = None):
|
|
5496
5584
|
|
|
5497
5585
|
community_dict = invert_dict(self.communities)
|
|
5498
5586
|
summation = 0
|
|
5499
|
-
id_set =
|
|
5587
|
+
id_set = iden_set(self.node_identities.values())
|
|
5500
5588
|
id_dict = {}
|
|
5501
5589
|
for i, iden in enumerate(id_set):
|
|
5502
5590
|
id_dict[iden] = i
|
|
@@ -5515,7 +5603,12 @@ class Network_3D:
|
|
|
5515
5603
|
|
|
5516
5604
|
# Count identities in this community
|
|
5517
5605
|
for node in nodes:
|
|
5518
|
-
|
|
5606
|
+
try:
|
|
5607
|
+
idens = ast.literal_eval(self.node_identities[node])
|
|
5608
|
+
for iden in idens:
|
|
5609
|
+
counter[id_dict[iden]] += 1
|
|
5610
|
+
except:
|
|
5611
|
+
counter[id_dict[self.node_identities[node]]] += 1 # Keep them as arrays
|
|
5519
5612
|
|
|
5520
5613
|
for i in range(len(counter)): # Translate them into proportions out of 1
|
|
5521
5614
|
|
|
@@ -5527,12 +5620,11 @@ class Network_3D:
|
|
|
5527
5620
|
umap_dict[community] = counter
|
|
5528
5621
|
|
|
5529
5622
|
else:
|
|
5530
|
-
idens =
|
|
5623
|
+
idens = invert_dict_special(self.node_identities)
|
|
5531
5624
|
iden_count = {}
|
|
5532
5625
|
template = {}
|
|
5533
5626
|
node_count = len(list(self.communities.keys()))
|
|
5534
5627
|
|
|
5535
|
-
|
|
5536
5628
|
for iden in id_set:
|
|
5537
5629
|
template[iden] = 0
|
|
5538
5630
|
|
|
@@ -5548,7 +5640,12 @@ class Network_3D:
|
|
|
5548
5640
|
counter = np.zeros(len(id_set))
|
|
5549
5641
|
|
|
5550
5642
|
for node in nodes:
|
|
5551
|
-
|
|
5643
|
+
try:
|
|
5644
|
+
idents = ast.literal_eval(self.node_identities[node])
|
|
5645
|
+
for iden in idents:
|
|
5646
|
+
iden_tracker[iden] += 1
|
|
5647
|
+
except:
|
|
5648
|
+
iden_tracker[self.node_identities[node]] += 1
|
|
5552
5649
|
|
|
5553
5650
|
i = 0
|
|
5554
5651
|
|
|
@@ -5585,7 +5682,10 @@ class Network_3D:
|
|
|
5585
5682
|
if self.communities is not None and label == 2:
|
|
5586
5683
|
neighbor_group = {}
|
|
5587
5684
|
for node, com in self.communities.items():
|
|
5588
|
-
|
|
5685
|
+
try:
|
|
5686
|
+
neighbor_group[com] = neighbors[node]
|
|
5687
|
+
except:
|
|
5688
|
+
neighbor_group[com] = 0
|
|
5589
5689
|
neighborhoods.visualize_cluster_composition_umap(umap_dict, id_set, neighborhoods = neighbor_group)
|
|
5590
5690
|
elif label == 1:
|
|
5591
5691
|
neighborhoods.visualize_cluster_composition_umap(umap_dict, id_set, label = True)
|
|
@@ -5816,7 +5916,7 @@ class Network_3D:
|
|
|
5816
5916
|
return heat_dict, overlay
|
|
5817
5917
|
|
|
5818
5918
|
|
|
5819
|
-
def merge_node_ids(self, path, data):
|
|
5919
|
+
def merge_node_ids(self, path, data, include = True):
|
|
5820
5920
|
|
|
5821
5921
|
if self.node_identities is None: # Prepare modular dict
|
|
5822
5922
|
|
|
@@ -5825,9 +5925,17 @@ class Network_3D:
|
|
|
5825
5925
|
nodes = list(np.unique(data))
|
|
5826
5926
|
if 0 in nodes:
|
|
5827
5927
|
del nodes[0]
|
|
5828
|
-
|
|
5829
5928
|
for node in nodes:
|
|
5830
|
-
|
|
5929
|
+
|
|
5930
|
+
self.node_identities[node] = [] # Assign to lists at first
|
|
5931
|
+
else:
|
|
5932
|
+
for node, iden in self.node_identities.items():
|
|
5933
|
+
try:
|
|
5934
|
+
self.node_identities[node] = ast.literal_eval(iden)
|
|
5935
|
+
except:
|
|
5936
|
+
self.node_identities[node] = [iden]
|
|
5937
|
+
|
|
5938
|
+
|
|
5831
5939
|
|
|
5832
5940
|
img_list = directory_info(path)
|
|
5833
5941
|
|
|
@@ -5837,9 +5945,12 @@ class Network_3D:
|
|
|
5837
5945
|
if len(np.unique(mask)) != 2:
|
|
5838
5946
|
|
|
5839
5947
|
mask = otsu_binarize(mask)
|
|
5948
|
+
else:
|
|
5949
|
+
mask = mask != 0
|
|
5840
5950
|
|
|
5841
5951
|
nodes = data * mask
|
|
5842
|
-
nodes =
|
|
5952
|
+
nodes = np.unique(nodes)
|
|
5953
|
+
nodes = nodes.tolist()
|
|
5843
5954
|
if 0 in nodes:
|
|
5844
5955
|
del nodes[0]
|
|
5845
5956
|
|
|
@@ -5850,21 +5961,43 @@ class Network_3D:
|
|
|
5850
5961
|
else:
|
|
5851
5962
|
base_name = img
|
|
5852
5963
|
|
|
5964
|
+
assigned = {}
|
|
5965
|
+
|
|
5966
|
+
|
|
5853
5967
|
for node in self.node_identities.keys():
|
|
5854
5968
|
|
|
5855
5969
|
try:
|
|
5856
5970
|
|
|
5857
|
-
if node in nodes:
|
|
5971
|
+
if int(node) in nodes:
|
|
5858
5972
|
|
|
5859
|
-
self.node_identities[node]
|
|
5973
|
+
self.node_identities[node].append(f'{base_name}+')
|
|
5860
5974
|
|
|
5861
|
-
|
|
5975
|
+
elif include:
|
|
5862
5976
|
|
|
5863
|
-
self.node_identities[node]
|
|
5977
|
+
self.node_identities[node].append(f'{base_name}-')
|
|
5864
5978
|
|
|
5865
5979
|
except:
|
|
5866
5980
|
pass
|
|
5867
5981
|
|
|
5982
|
+
modify_dict = copy.deepcopy(self.node_identities)
|
|
5983
|
+
|
|
5984
|
+
for node, iden in self.node_identities.items():
|
|
5985
|
+
|
|
5986
|
+
try:
|
|
5987
|
+
|
|
5988
|
+
if len(iden) == 1:
|
|
5989
|
+
|
|
5990
|
+
modify_dict[node] = str(iden[0]) # Singleton lists become bare strings
|
|
5991
|
+
elif len(iden) == 0:
|
|
5992
|
+
del modify_dict[node]
|
|
5993
|
+
else:
|
|
5994
|
+
modify_dict[node] = str(iden) # We hold multi element lists as strings for compatibility
|
|
5995
|
+
|
|
5996
|
+
except:
|
|
5997
|
+
pass
|
|
5998
|
+
|
|
5999
|
+
self.node_identities = modify_dict
|
|
6000
|
+
|
|
5868
6001
|
|
|
5869
6002
|
def nearest_neighbors_avg(self, root, targ, xy_scale = 1, z_scale = 1, num = 1, heatmap = False, threed = True, numpy = False, quant = False, centroids = True):
|
|
5870
6003
|
|
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -4011,7 +4011,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4011
4011
|
id_code_action = overlay_menu.addAction("Code Identities")
|
|
4012
4012
|
id_code_action.triggered.connect(lambda: self.show_code_dialog(sort = 'Identity'))
|
|
4013
4013
|
umap_action = overlay_menu.addAction("Centroid UMAP")
|
|
4014
|
-
umap_action.triggered.connect(self.
|
|
4014
|
+
umap_action.triggered.connect(self.handle_centroid_umap)
|
|
4015
|
+
iden_umap_action = overlay_menu.addAction("Identity UMAP (If any nodes were assigned multiple identities)")
|
|
4016
|
+
iden_umap_action.triggered.connect(self.handle_iden_umap)
|
|
4015
4017
|
|
|
4016
4018
|
rand_menu = analysis_menu.addMenu("Randomize")
|
|
4017
4019
|
random_action = rand_menu.addAction("Generate Equivalent Random Network")
|
|
@@ -4542,6 +4544,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4542
4544
|
if not self.confirm_calcbranch_dialog("Use of this feature will require additional use of the Nodes and Overlay 2 channels. Please save any data and return, or proceed if you do not need those channels' data"):
|
|
4543
4545
|
return
|
|
4544
4546
|
|
|
4547
|
+
if my_network.edges is None and my_network.nodes is not None:
|
|
4548
|
+
self.load_channel(1, my_network.nodes, data = True)
|
|
4549
|
+
self.delete_channel(0, False)
|
|
4550
|
+
|
|
4545
4551
|
my_network.id_overlay = my_network.edges.copy()
|
|
4546
4552
|
|
|
4547
4553
|
self.show_gennodes_dialog()
|
|
@@ -4574,6 +4580,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4574
4580
|
if not self.confirm_calcbranch_dialog("Use of this feature will require additional use of the Nodes and Overlay 2 channels. Please save any data and return, or proceed if you do not need those channels' data"):
|
|
4575
4581
|
return
|
|
4576
4582
|
|
|
4583
|
+
if my_network.edges is None and my_network.nodes is not None:
|
|
4584
|
+
self.load_channel(1, my_network.nodes, data = True)
|
|
4585
|
+
self.delete_channel(0, False)
|
|
4586
|
+
|
|
4577
4587
|
self.show_branch_dialog(called = True)
|
|
4578
4588
|
|
|
4579
4589
|
self.load_channel(0, my_network.edges, data = True)
|
|
@@ -5850,8 +5860,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5850
5860
|
y_max = min(min_height, int(np.ceil(current_ylim[0] + 0.5)))
|
|
5851
5861
|
|
|
5852
5862
|
if self.pan_mode:
|
|
5853
|
-
box_len = (x_max - x_min)
|
|
5854
|
-
box_height = (y_max - y_min)
|
|
5863
|
+
box_len = int((x_max - x_min)/2)
|
|
5864
|
+
box_height = int((y_max - y_min)/2)
|
|
5855
5865
|
x_min = max(0, x_min - box_len)
|
|
5856
5866
|
x_max = min(self.shape[2], x_max + box_len)
|
|
5857
5867
|
y_min = max(0, y_min - box_height)
|
|
@@ -6187,13 +6197,20 @@ class ImageViewerWindow(QMainWindow):
|
|
|
6187
6197
|
dialog = CodeDialog(self, sort = sort)
|
|
6188
6198
|
dialog.exec()
|
|
6189
6199
|
|
|
6190
|
-
def
|
|
6200
|
+
def handle_centroid_umap(self):
|
|
6191
6201
|
|
|
6192
6202
|
if my_network.node_centroids is None:
|
|
6193
6203
|
self.show_centroid_dialog()
|
|
6194
6204
|
|
|
6195
6205
|
my_network.centroid_umap()
|
|
6196
6206
|
|
|
6207
|
+
def handle_iden_umap(self):
|
|
6208
|
+
|
|
6209
|
+
if my_network.node_identities is None:
|
|
6210
|
+
return
|
|
6211
|
+
|
|
6212
|
+
my_network.identity_umap()
|
|
6213
|
+
|
|
6197
6214
|
def closeEvent(self, event):
|
|
6198
6215
|
"""Override closeEvent to close all windows when main window closes"""
|
|
6199
6216
|
|
|
@@ -7460,6 +7477,12 @@ class MergeNodeIdDialog(QDialog):
|
|
|
7460
7477
|
self.z_scale = QLineEdit(f"{my_network.z_scale}")
|
|
7461
7478
|
layout.addRow("z_scale:", self.z_scale)
|
|
7462
7479
|
|
|
7480
|
+
# Add Run button
|
|
7481
|
+
self.include = QPushButton("Include Negative Gates?")
|
|
7482
|
+
self.include.setCheckable(True)
|
|
7483
|
+
self.include.setChecked(True)
|
|
7484
|
+
layout.addWidget(self.include)
|
|
7485
|
+
|
|
7463
7486
|
# Add Run button
|
|
7464
7487
|
run_button = QPushButton("Get Directory")
|
|
7465
7488
|
run_button.clicked.connect(self.run)
|
|
@@ -7475,6 +7498,7 @@ class MergeNodeIdDialog(QDialog):
|
|
|
7475
7498
|
|
|
7476
7499
|
|
|
7477
7500
|
data = self.parent().channel_data[0]
|
|
7501
|
+
include = self.include.isChecked()
|
|
7478
7502
|
|
|
7479
7503
|
if data is None:
|
|
7480
7504
|
return
|
|
@@ -7493,7 +7517,7 @@ class MergeNodeIdDialog(QDialog):
|
|
|
7493
7517
|
if search > 0:
|
|
7494
7518
|
data = sdl.smart_dilate(data, 1, 1, GPU = False, fast_dil = False, use_dt_dil_amount = search, xy_scale = xy_scale, z_scale = z_scale)
|
|
7495
7519
|
|
|
7496
|
-
my_network.merge_node_ids(selected_path, data)
|
|
7520
|
+
my_network.merge_node_ids(selected_path, data, include)
|
|
7497
7521
|
|
|
7498
7522
|
self.parent().format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity')
|
|
7499
7523
|
|
|
@@ -12453,6 +12477,10 @@ class GenNodesDialog(QDialog):
|
|
|
12453
12477
|
def run_gennodes(self):
|
|
12454
12478
|
|
|
12455
12479
|
try:
|
|
12480
|
+
|
|
12481
|
+
if my_network.edges is None and my_network.nodes is not None:
|
|
12482
|
+
self.parent().load_channel(1, my_network.nodes, data = True)
|
|
12483
|
+
self.parent().delete_channel(0, True)
|
|
12456
12484
|
# Get directory (None if empty)
|
|
12457
12485
|
#directory = self.directory.text() if self.directory.text() else None
|
|
12458
12486
|
|
|
@@ -12682,6 +12710,10 @@ class BranchDialog(QDialog):
|
|
|
12682
12710
|
fix_val = float(self.fix_val.text()) if self.fix_val.text() else None
|
|
12683
12711
|
seed = int(self.seed.text()) if self.seed.text() else None
|
|
12684
12712
|
|
|
12713
|
+
if my_network.edges is None and my_network.nodes is not None:
|
|
12714
|
+
self.parent().load_channel(1, my_network.nodes, data = True)
|
|
12715
|
+
self.parent().delete_channel(0, True)
|
|
12716
|
+
|
|
12685
12717
|
original_shape = my_network.edges.shape
|
|
12686
12718
|
original_array = copy.deepcopy(my_network.edges)
|
|
12687
12719
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nettracer3d
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.4
|
|
4
4
|
Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
|
|
5
5
|
Author-email: Liam McLaughlin <liamm@wustl.edu>
|
|
6
6
|
Project-URL: Documentation, https://nettracer3d.readthedocs.io/en/latest/
|
|
@@ -110,8 +110,6 @@ McLaughlin, L., Zhang, B., Sharma, S. et al. Three dimensional multiscalar neuro
|
|
|
110
110
|
|
|
111
111
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
112
112
|
|
|
113
|
-
-- Version 0.9.
|
|
114
|
-
|
|
115
|
-
*
|
|
116
|
-
* Adjusted pan mode
|
|
117
|
-
* Some bug fixes.
|
|
113
|
+
-- Version 0.9.4 Updates --
|
|
114
|
+
|
|
115
|
+
* Added some compatibility for nodes being assigned 'multiple identities'
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
nettracer3d/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
nettracer3d/cellpose_manager.py,sha256=NfRqW6Zl7yRU4qHCS_KjmR0R6QANSSgCO0_dr-eivxg,6694
|
|
3
3
|
nettracer3d/community_extractor.py,sha256=QG2v2y9lKb07LRU0zXCAlA_z-96BNqLHh5xP17KLmYA,28147
|
|
4
|
-
nettracer3d/excelotron.py,sha256=
|
|
4
|
+
nettracer3d/excelotron.py,sha256=aNof6k-DgMxVyFgsl3ltSCxG4vZW49cuvCBzfzhYhUY,75072
|
|
5
5
|
nettracer3d/modularity.py,sha256=pborVcDBvICB2-g8lNoSVZbIReIBlfeBmjFbPYmtq7Y,22443
|
|
6
6
|
nettracer3d/morphology.py,sha256=jyDjYzrZ4LvI5jOyw8DLsxmo-i5lpqHsejYpW7Tq7Mo,19786
|
|
7
|
-
nettracer3d/neighborhoods.py,sha256=
|
|
8
|
-
nettracer3d/nettracer.py,sha256=
|
|
9
|
-
nettracer3d/nettracer_gui.py,sha256=
|
|
7
|
+
nettracer3d/neighborhoods.py,sha256=2QzGKkQCSUerxigkcHF4iN7ys8kv-KHTbkD_9_2Krgo,58365
|
|
8
|
+
nettracer3d/nettracer.py,sha256=wraH1Ba61bA0awmBfH71u_hkKcytUG_pISkJX6iP-ic,268658
|
|
9
|
+
nettracer3d/nettracer_gui.py,sha256=tYg0wDV2p6b0czvozMrrpja7fbx3nSBgjR2ZV4b5vBE,601760
|
|
10
10
|
nettracer3d/network_analysis.py,sha256=kBzsVaq4dZkMe0k-VGvQIUvM-tK0ZZ8bvb-wtsugZRQ,46150
|
|
11
11
|
nettracer3d/network_draw.py,sha256=F7fw6Pcf4qWOhdKwLmhwqWdschbDlHzwCVolQC9imeU,14117
|
|
12
12
|
nettracer3d/node_draw.py,sha256=kZcR1PekLg0riioNeGcALIXQyZ5PtHA_9MT6z7Zovdk,10401
|
|
@@ -17,9 +17,9 @@ nettracer3d/segmenter.py,sha256=-Llkhp3TlAIBXZNhcfMFQRdg0vec1xtlOm0c4_bSU9U,7576
|
|
|
17
17
|
nettracer3d/segmenter_GPU.py,sha256=optCZ_zLIfe99rgqmyKWUZlWW5TF5jEC_C3keu1m7VQ,77771
|
|
18
18
|
nettracer3d/simple_network.py,sha256=dkG4jpc4zzdeuoaQobgGfL3PNo6N8dGKQ5hEEubFIvA,9947
|
|
19
19
|
nettracer3d/smart_dilate.py,sha256=TvRUh6B4q4zIdCO1BWH-xgTdND5OUNmo99eyxG9oIAU,27145
|
|
20
|
-
nettracer3d-0.9.
|
|
21
|
-
nettracer3d-0.9.
|
|
22
|
-
nettracer3d-0.9.
|
|
23
|
-
nettracer3d-0.9.
|
|
24
|
-
nettracer3d-0.9.
|
|
25
|
-
nettracer3d-0.9.
|
|
20
|
+
nettracer3d-0.9.4.dist-info/licenses/LICENSE,sha256=jnNT-yBeIAKAHpYthPvLeqCzJ6nSurgnKmloVnfsjCI,764
|
|
21
|
+
nettracer3d-0.9.4.dist-info/METADATA,sha256=-xNx22dCKedpkF7uLVduYpMYBCJcXv_2xssw34nEo4c,7048
|
|
22
|
+
nettracer3d-0.9.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
nettracer3d-0.9.4.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
|
|
24
|
+
nettracer3d-0.9.4.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
|
|
25
|
+
nettracer3d-0.9.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|