nettracer3d 0.8.3__py3-none-any.whl → 0.8.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nettracer3d might be problematic. Click here for more details.

@@ -12,6 +12,7 @@ import random
12
12
  from . import node_draw
13
13
 
14
14
 
15
+
15
16
  def binarize(image):
16
17
  """Convert an array from numerical values to boolean mask"""
17
18
  image = image != 0
@@ -462,7 +463,6 @@ def get_color_name_mapping():
462
463
  'bright_cyan': (0, 255, 255),
463
464
  'dark_teal': (0, 128, 128),
464
465
  'turquoise': (64, 224, 208),
465
- 'aqua': (0, 255, 255),
466
466
  'seafoam': (159, 226, 191),
467
467
  'teal_blue': (54, 117, 136),
468
468
 
@@ -655,7 +655,7 @@ def assign_node_colors(node_list: List[int], labeled_array: np.ndarray) -> Tuple
655
655
 
656
656
  # Create lookup table
657
657
  max_label = max(max(labeled_array.flat), max(node_list) if node_list else 0)
658
- color_lut = np.zeros((max_label + 1, 4), dtype=np.uint8) # Transparent by default
658
+ color_lut = np.zeros((int(max_label) + 1, 4), dtype=np.uint8) # Transparent by default
659
659
 
660
660
  for node_id, color in node_to_color.items():
661
661
  color_lut[node_id] = color
@@ -685,7 +685,7 @@ def assign_community_colors(community_dict: Dict[int, int], labeled_array: np.nd
685
685
 
686
686
  # Create lookup table - this is the key optimization
687
687
  max_label = max(max(labeled_array.flat), max(node_to_color.keys()) if node_to_color else 0)
688
- color_lut = np.zeros((max_label + 1, 4), dtype=np.uint8) # Transparent by default
688
+ color_lut = np.zeros((int(max_label) + 1, 4), dtype=np.uint8) # Transparent by default
689
689
 
690
690
  for node_id, color in node_to_color.items():
691
691
  color_lut[node_id] = color
nettracer3d/excelotron.py CHANGED
@@ -1238,7 +1238,7 @@ class TabbedIdentityWidget(QFrame):
1238
1238
 
1239
1239
  class ExcelToDictGUI(QMainWindow):
1240
1240
  # Add this signal
1241
- data_exported = pyqtSignal(dict, str) # dictionary, property_name
1241
+ data_exported = pyqtSignal(dict, str, bool) # dictionary, property_name, add_status
1242
1242
 
1243
1243
  def __init__(self):
1244
1244
  super().__init__()
@@ -1255,6 +1255,7 @@ class ExcelToDictGUI(QMainWindow):
1255
1255
 
1256
1256
  self.setWindowTitle("Excel to Python Dictionary Converter")
1257
1257
  self.setGeometry(100, 100, 1200, 800)
1258
+ self.add = False
1258
1259
 
1259
1260
  self.setup_ui()
1260
1261
 
@@ -1414,6 +1415,14 @@ class ExcelToDictGUI(QMainWindow):
1414
1415
  self.export_btn.clicked.connect(self.export_dictionary)
1415
1416
  export_layout.addWidget(self.export_btn)
1416
1417
 
1418
+ self.add_button = QPushButton("+")
1419
+ self.add_button.setFixedSize(20, 20)
1420
+ self.add_button.setCheckable(True)
1421
+ self.add_button.setChecked(False)
1422
+ self.add_button.clicked.connect(self.toggle_add)
1423
+ export_layout.addWidget(self.add_button)
1424
+
1425
+
1417
1426
  right_layout.addLayout(export_layout)
1418
1427
 
1419
1428
  right_widget.setLayout(right_layout)
@@ -1431,6 +1440,15 @@ class ExcelToDictGUI(QMainWindow):
1431
1440
  # Add splitter to main layout
1432
1441
  main_layout.addWidget(splitter)
1433
1442
 
1443
+ def toggle_add(self):
1444
+
1445
+ if self.add_button.isChecked():
1446
+ print("Exported Properties will be added onto existing ones")
1447
+ self.add = True
1448
+ else:
1449
+ print("Exported Properties will be override existing ones")
1450
+ self.add = False
1451
+
1434
1452
  def load_template(self, template_name):
1435
1453
  if template_name in self.templates:
1436
1454
  # Clear existing columns
@@ -1681,12 +1699,13 @@ class ExcelToDictGUI(QMainWindow):
1681
1699
  return
1682
1700
 
1683
1701
  # Emit signal to parent application
1684
- self.data_exported.emit(result_dict, property_name)
1702
+ self.data_exported.emit(result_dict, property_name, self.add)
1685
1703
 
1686
1704
  # Still store in global variables for backward compatibility
1687
1705
  import builtins
1688
1706
  builtins.excel_dict = result_dict
1689
1707
  builtins.target_property = property_name
1708
+ builtins.add = self.add
1690
1709
 
1691
1710
  # Show success message with preview
1692
1711
  preview = str(result_dict)
@@ -2,11 +2,13 @@ import numpy as np
2
2
  from sklearn.cluster import KMeans
3
3
  from sklearn.metrics import calinski_harabasz_score
4
4
  import matplotlib.pyplot as plt
5
- from typing import Dict, Set
5
+ from typing import Dict, Set, List, Tuple, Optional
6
6
  import umap
7
7
  from matplotlib.colors import LinearSegmentedColormap
8
8
  from sklearn.cluster import DBSCAN
9
9
  from sklearn.neighbors import NearestNeighbors
10
+ import matplotlib.colors as mcolors
11
+
10
12
 
11
13
 
12
14
  import os
@@ -308,11 +310,43 @@ def plot_dict_heatmap(unsorted_data_dict, id_set, figsize=(12, 8), title="Neighb
308
310
 
309
311
 
310
312
 
313
+ def generate_distinct_colors(n_colors: int) -> List[str]:
314
+ """
315
+ Generate visually distinct colors using matplotlib's tab and Set colormaps.
316
+ Falls back to HSV generation for large numbers of colors.
317
+
318
+ Args:
319
+ n_colors: Number of distinct colors needed
320
+
321
+ Returns:
322
+ List of color strings compatible with matplotlib
323
+ """
324
+ if n_colors <= 10:
325
+ # Use tab10 colormap for up to 10 colors
326
+ colors = plt.cm.tab10(np.linspace(0, 1, min(n_colors, 10)))
327
+ return [mcolors.rgb2hex(color) for color in colors[:n_colors]]
328
+ elif n_colors <= 20:
329
+ # Use tab20 for up to 20 colors
330
+ colors = plt.cm.tab20(np.linspace(0, 1, min(n_colors, 20)))
331
+ return [mcolors.rgb2hex(color) for color in colors[:n_colors]]
332
+ else:
333
+ # For larger numbers, use HSV space
334
+ colors = []
335
+ for i in range(n_colors):
336
+ hue = i / n_colors
337
+ color = mcolors.hsv_to_rgb([hue, 0.8, 0.9]) # High saturation and value
338
+ colors.append(mcolors.rgb2hex(color))
339
+ return colors
340
+
341
+
311
342
  def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
312
343
  class_names: Set[str],
313
- label = False,
344
+ label: bool = False,
314
345
  n_components: int = 2,
315
- random_state: int = 42):
346
+ random_state: int = 42,
347
+ id_dictionary: Optional[Dict[int, str]] = None,
348
+ graph_label = "Community ID",
349
+ title = 'UMAP Visualization of Community Compositions'):
316
350
  """
317
351
  Convert cluster composition data to UMAP visualization.
318
352
 
@@ -323,10 +357,15 @@ def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
323
357
  representing the composition of each cluster
324
358
  class_names : set
325
359
  Set of strings representing the class names (order corresponds to array indices)
360
+ label : bool
361
+ Whether to show cluster ID labels on the plot
326
362
  n_components : int
327
363
  Number of UMAP components (default: 2 for 2D visualization)
328
364
  random_state : int
329
365
  Random state for reproducibility
366
+ id_dictionary : dict, optional
367
+ Dictionary mapping cluster IDs to identity names. If provided, colors will be
368
+ assigned by identity rather than cluster ID, and a legend will be shown.
330
369
 
331
370
  Returns:
332
371
  --------
@@ -335,7 +374,10 @@ def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
335
374
  """
336
375
 
337
376
  # Convert set to sorted list for consistent ordering
338
- class_labels = sorted(list(class_names))
377
+ try:
378
+ class_labels = sorted(list(class_names))
379
+ except:
380
+ class_labels = None
339
381
 
340
382
  # Extract cluster IDs and compositions
341
383
  cluster_ids = list(cluster_data.keys())
@@ -347,57 +389,124 @@ def visualize_cluster_composition_umap(cluster_data: Dict[int, np.ndarray],
347
389
  # Fit and transform the composition data
348
390
  embedding = reducer.fit_transform(compositions)
349
391
 
392
+ # Prepare colors and labels based on whether id_dictionary is provided
393
+ if id_dictionary is not None:
394
+ # Get unique identities and assign colors (group all unknown into single "Unknown" category)
395
+ identities = [id_dictionary.get(cluster_id, "Unknown") for cluster_id in cluster_ids]
396
+ unique_identities = sorted(list(set(identities)))
397
+ colors = generate_distinct_colors(len(unique_identities))
398
+ identity_to_color = {identity: colors[i] for i, identity in enumerate(unique_identities)}
399
+ point_colors = [identity_to_color[identity] for identity in identities]
400
+ use_identity_coloring = True
401
+ else:
402
+ # Use default cluster ID coloring
403
+ point_colors = cluster_ids
404
+ use_identity_coloring = False
405
+
350
406
  # Create visualization
351
- plt.figure(figsize=(10, 8))
407
+ plt.figure(figsize=(12, 8))
352
408
 
353
409
  if n_components == 2:
354
- scatter = plt.scatter(embedding[:, 0], embedding[:, 1],
355
- c=cluster_ids, cmap='viridis', s=100, alpha=0.7)
410
+ if use_identity_coloring:
411
+ scatter = plt.scatter(embedding[:, 0], embedding[:, 1],
412
+ c=point_colors, s=100, alpha=0.7)
413
+ else:
414
+ scatter = plt.scatter(embedding[:, 0], embedding[:, 1],
415
+ c=point_colors, cmap='viridis', s=100, alpha=0.7)
356
416
 
357
417
  if label:
358
418
  # Add cluster ID labels
359
419
  for i, cluster_id in enumerate(cluster_ids):
360
- plt.annotate(f'{cluster_id}',
420
+ display_label = f'{cluster_id}'
421
+ if id_dictionary is not None:
422
+ identity = id_dictionary.get(cluster_id, "Unknown")
423
+ display_label = f'{cluster_id}\n({identity})'
424
+
425
+ plt.annotate(display_label,
361
426
  (embedding[i, 0], embedding[i, 1]),
362
427
  xytext=(5, 5), textcoords='offset points',
363
- fontsize=9, alpha=0.8)
428
+ fontsize=9, alpha=0.8, ha='left')
429
+
430
+ # Add appropriate legend/colorbar
431
+ if use_identity_coloring:
432
+ # Create custom legend for identities
433
+ legend_elements = [plt.Line2D([0], [0], marker='o', color='w',
434
+ markerfacecolor=identity_to_color[identity],
435
+ markersize=10, label=identity)
436
+ for identity in unique_identities]
437
+ plt.legend(handles=legend_elements, title='Identity',
438
+ bbox_to_anchor=(1.05, 1), loc='upper left')
439
+ else:
440
+ plt.colorbar(scatter, label=graph_label)
364
441
 
365
- plt.colorbar(scatter, label='Community ID')
366
442
  plt.xlabel('UMAP Component 1')
367
443
  plt.ylabel('UMAP Component 2')
368
- plt.title('UMAP Visualization of Community Compositions')
444
+ if use_identity_coloring:
445
+ title += ' (Colored by Identity)'
446
+ plt.title(title)
369
447
 
370
448
  elif n_components == 3:
371
- fig = plt.figure(figsize=(12, 9))
449
+ fig = plt.figure(figsize=(14, 10))
372
450
  ax = fig.add_subplot(111, projection='3d')
373
- scatter = ax.scatter(embedding[:, 0], embedding[:, 1], embedding[:, 2],
374
- c=cluster_ids, cmap='viridis', s=100, alpha=0.7)
375
451
 
376
- # Add cluster ID labels
377
- for i, cluster_id in enumerate(cluster_ids):
378
- ax.text(embedding[i, 0], embedding[i, 1], embedding[i, 2],
379
- f'C{cluster_id}', fontsize=8)
452
+ if use_identity_coloring:
453
+ scatter = ax.scatter(embedding[:, 0], embedding[:, 1], embedding[:, 2],
454
+ c=point_colors, s=100, alpha=0.7)
455
+ else:
456
+ scatter = ax.scatter(embedding[:, 0], embedding[:, 1], embedding[:, 2],
457
+ c=point_colors, cmap='viridis', s=100, alpha=0.7)
458
+
459
+ if label:
460
+ # Add cluster ID labels
461
+ for i, cluster_id in enumerate(cluster_ids):
462
+ display_label = f'C{cluster_id}'
463
+ if id_dictionary is not None:
464
+ identity = id_dictionary.get(cluster_id, "Unknown")
465
+ display_label = f'C{cluster_id}\n({identity})'
466
+
467
+ ax.text(embedding[i, 0], embedding[i, 1], embedding[i, 2],
468
+ display_label, fontsize=8)
380
469
 
381
470
  ax.set_xlabel('UMAP Component 1')
382
471
  ax.set_ylabel('UMAP Component 2')
383
472
  ax.set_zlabel('UMAP Component 3')
384
- ax.set_title('3D UMAP Visualization of Cluster Compositions')
385
- plt.colorbar(scatter, label='Cluster ID')
473
+ title = '3D UMAP Visualization of Cluster Compositions'
474
+ if use_identity_coloring:
475
+ title += ' (Colored by Identity)'
476
+ ax.set_title(title)
477
+
478
+ # Add appropriate legend/colorbar
479
+ if use_identity_coloring:
480
+ # Create custom legend for identities
481
+ legend_elements = [plt.Line2D([0], [0], marker='o', color='w',
482
+ markerfacecolor=identity_to_color[identity],
483
+ markersize=10, label=identity)
484
+ for identity in unique_identities]
485
+ ax.legend(handles=legend_elements, title='Identity',
486
+ bbox_to_anchor=(1.05, 1), loc='upper left')
487
+ else:
488
+ plt.colorbar(scatter, label='Cluster ID')
386
489
 
387
490
  plt.tight_layout()
388
491
  plt.show()
389
492
 
390
- # Print composition details
391
- print("Cluster Compositions:")
392
- print(f"Classes: {class_labels}")
393
- for i, cluster_id in enumerate(cluster_ids):
394
- composition = compositions[i]
395
- print(f"Cluster {cluster_id}: {composition}")
396
- # Show which classes dominate this cluster
397
- dominant_indices = np.argsort(composition)[::-1][:2] # Top 2
398
- dominant_classes = [class_labels[idx] for idx in dominant_indices]
399
- dominant_values = [composition[idx] for idx in dominant_indices]
400
- print(f" Dominant: {dominant_classes[0]} ({dominant_values[0]:.3f}), {dominant_classes[1]} ({dominant_values[1]:.3f})")
493
+ if class_labels is not None:
494
+ # Print composition details
495
+ print("Cluster Compositions:")
496
+ print(f"Classes: {class_labels}")
497
+ for i, cluster_id in enumerate(cluster_ids):
498
+ composition = compositions[i]
499
+ identity_info = ""
500
+ if id_dictionary is not None:
501
+ identity = id_dictionary.get(cluster_id, "Unknown")
502
+ identity_info = f" (Identity: {identity})"
503
+
504
+ print(f"Cluster {cluster_id}{identity_info}: {composition}")
505
+ # Show which classes dominate this cluster
506
+ dominant_indices = np.argsort(composition)[::-1][:2] # Top 2
507
+ dominant_classes = [class_labels[idx] for idx in dominant_indices]
508
+ dominant_values = [composition[idx] for idx in dominant_indices]
509
+ print(f" Dominant: {dominant_classes[0]} ({dominant_values[0]:.3f}), {dominant_classes[1]} ({dominant_values[1]:.3f})")
401
510
 
402
511
  return embedding
403
512