nettracer3d 0.3.4__tar.gz → 0.3.5__tar.gz

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.
Files changed (23) hide show
  1. {nettracer3d-0.3.4/src/nettracer3d.egg-info → nettracer3d-0.3.5}/PKG-INFO +1 -1
  2. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/pyproject.toml +1 -1
  3. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/nettracer.py +7 -4
  4. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/nettracer_gui.py +188 -152
  5. {nettracer3d-0.3.4 → nettracer3d-0.3.5/src/nettracer3d.egg-info}/PKG-INFO +1 -1
  6. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/LICENSE +0 -0
  7. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/README.md +0 -0
  8. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/setup.cfg +0 -0
  9. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/__init__.py +0 -0
  10. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/community_extractor.py +0 -0
  11. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/hub_getter.py +0 -0
  12. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/modularity.py +0 -0
  13. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/morphology.py +0 -0
  14. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/network_analysis.py +0 -0
  15. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/network_draw.py +0 -0
  16. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/node_draw.py +0 -0
  17. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/proximity.py +0 -0
  18. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/simple_network.py +0 -0
  19. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d/smart_dilate.py +0 -0
  20. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d.egg-info/SOURCES.txt +0 -0
  21. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
  22. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d.egg-info/requires.txt +0 -0
  23. {nettracer3d-0.3.4 → nettracer3d-0.3.5}/src/nettracer3d.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: nettracer3d
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
5
5
  Author-email: Liam McLaughlin <boom2449@gmail.com>
6
6
  Project-URL: User_Manual, https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nettracer3d"
3
- version = "0.3.4"
3
+ version = "0.3.5"
4
4
  authors = [
5
5
  { name="Liam McLaughlin", email="boom2449@gmail.com" },
6
6
  ]
@@ -1524,7 +1524,7 @@ def skeletonize(arrayimage, directory = None):
1524
1524
 
1525
1525
  return arrayimage
1526
1526
 
1527
- def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = None, directory = None, nodes = None, bonus_array = None, GPU = True):
1527
+ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = None, directory = None, nodes = None, bonus_array = None, GPU = True, arrayshape = None):
1528
1528
  """
1529
1529
  Can be used to label branches a binary image. Labelled output will be saved to the active directory if none is specified. Note this works better on already thin filaments and may over-divide larger trunkish objects.
1530
1530
  :param array: (Mandatory, string or ndarray) - If string, a path to a tif file to label. Note that the ndarray alternative is for internal use mainly and will not save its output.
@@ -1546,7 +1546,7 @@ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
1546
1546
  array = downsample(array, down_factor)
1547
1547
  arrayshape = array.shape
1548
1548
  else:
1549
- arrayshape = bonus_array.shape
1549
+ arrayshape = arrayshape
1550
1550
 
1551
1551
 
1552
1552
  if nodes is None:
@@ -1564,6 +1564,7 @@ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
1564
1564
  array = upsample_with_padding(array, down_factor, arrayshape)
1565
1565
 
1566
1566
 
1567
+
1567
1568
  if nodes is None:
1568
1569
 
1569
1570
  array = smart_dilate.smart_label(array, other_array, GPU = GPU)
@@ -1594,7 +1595,7 @@ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
1594
1595
 
1595
1596
  return array
1596
1597
 
1597
- def label_vertices(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = 0, directory = None, return_skele = False):
1598
+ def label_vertices(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = 0, directory = None, return_skele = False, order = 0):
1598
1599
  """
1599
1600
  Can be used to label vertices (where multiple branches connect) a binary image. Labelled output will be saved to the active directory if none is specified. Note this works better on already thin filaments and may over-divide larger trunkish objects.
1600
1601
  Note that this can be used in tandem with an edge segmentation to create an image containing 'pseudo-nodes', meaning we can make a network out of just a single edge file.
@@ -1616,7 +1617,9 @@ def label_vertices(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
1616
1617
 
1617
1618
  if down_factor > 0:
1618
1619
  array_shape = array.shape
1619
- array = downsample(array, down_factor)
1620
+ array = downsample(array, down_factor, order)
1621
+ if order == 3:
1622
+ array = binarize(array)
1620
1623
 
1621
1624
  array = array > 0
1622
1625
 
@@ -2514,17 +2514,10 @@ class ImageViewerWindow(QMainWindow):
2514
2514
  msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
2515
2515
  return msg.exec() == QMessageBox.StandardButton.Yes
2516
2516
 
2517
- def load_channel(self, channel_index, channel_data=None, data=False, assign_shape = True, preserve_zoom = None):
2517
+ def load_channel(self, channel_index, channel_data=None, data=False, assign_shape = True):
2518
2518
  """Load a channel and enable active channel selection if needed."""
2519
2519
 
2520
2520
  try:
2521
- # Store current zoom limits if they exist and weren't provided
2522
- if preserve_zoom is None and hasattr(self, 'ax'):
2523
- current_xlim = None
2524
- current_ylim = None
2525
- else:
2526
- current_xlim, current_ylim = preserve_zoom if preserve_zoom else (None, None)
2527
-
2528
2521
  if not data: # For solo loading
2529
2522
  import tifffile
2530
2523
  filename, _ = QFileDialog.getOpenFileName(
@@ -2605,13 +2598,7 @@ class ImageViewerWindow(QMainWindow):
2605
2598
  if assign_shape: #keep original shape tracked to undo resampling.
2606
2599
  self.original_shape = self.channel_data[channel_index].shape
2607
2600
 
2608
- # Restore zoom limits if they existed
2609
- if current_xlim is not None and current_ylim is not None:
2610
- self.ax.set_xlim(current_xlim)
2611
- self.ax.set_ylim(current_ylim)
2612
- self.update_display(preserve_zoom = (current_xlim, current_ylim))
2613
- else:
2614
- self.update_display()
2601
+ self.update_display()
2615
2602
 
2616
2603
 
2617
2604
 
@@ -2835,137 +2822,160 @@ class ImageViewerWindow(QMainWindow):
2835
2822
  self.channel_brightness[channel_index]['max'] = max_val / 255
2836
2823
  self.update_display(preserve_zoom = (current_xlim, current_ylim))
2837
2824
 
2838
- def update_display(self, preserve_zoom=None):
2839
- """Update the display with currently visible channels and highlight overlay."""
2840
- self.figure.clear()
2841
-
2842
- # Create subplot with tight layout and white figure background
2843
- self.figure.patch.set_facecolor('white')
2844
- self.ax = self.figure.add_subplot(111)
2845
-
2846
- # Store current zoom limits if they exist and weren't provided
2847
- if preserve_zoom is None and hasattr(self, 'ax'):
2848
- current_xlim = self.ax.get_xlim() if self.ax.get_xlim() != (0, 1) else None
2849
- current_ylim = self.ax.get_ylim() if self.ax.get_ylim() != (0, 1) else None
2825
+ def update_display(self, preserve_zoom=None, dims = None):
2826
+ """Update the display with currently visible channels and highlight overlay."""
2827
+
2828
+ self.figure.clear()
2829
+
2830
+ # Get active channels and their dimensions
2831
+ active_channels = [i for i in range(4) if self.channel_data[i] is not None]
2832
+ if dims is None:
2833
+ if active_channels:
2834
+ dims = [(self.channel_data[i].shape[1:3] if len(self.channel_data[i].shape) >= 3 else
2835
+ self.channel_data[i].shape) for i in active_channels]
2836
+ min_height = min(d[0] for d in dims)
2837
+ min_width = min(d[1] for d in dims)
2850
2838
  else:
2851
- current_xlim, current_ylim = preserve_zoom if preserve_zoom else (None, None)
2852
-
2853
- # Define base colors for each channel with increased intensity
2854
- base_colors = self.base_colors
2855
- # Set only the axes (image area) background to black
2856
- self.ax.set_facecolor('black')
2857
-
2858
- # Display each visible channel
2859
- for channel in range(4):
2860
- if (self.channel_visible[channel] and
2861
- self.channel_data[channel] is not None):
2862
-
2863
- # Check if we're dealing with RGB data
2864
- is_rgb = len(self.channel_data[channel].shape) == 4 and (self.channel_data[channel].shape[-1] == 3 or self.channel_data[channel].shape[-1] == 4)
2865
-
2866
- if len(self.channel_data[channel].shape) == 3 and not is_rgb:
2867
- current_image = self.channel_data[channel][self.current_slice, :, :]
2868
- elif is_rgb:
2869
- current_image = self.channel_data[channel][self.current_slice] # Already has RGB channels
2870
- else:
2871
- current_image = self.channel_data[channel]
2839
+ min_height = 1
2840
+ min_width = 1
2841
+ else:
2842
+ min_height = dims[0]
2843
+ min_width = dims[1]
2872
2844
 
2873
- if is_rgb and self.channel_data[channel].shape[-1] == 3:
2874
- # For RGB images, just display directly without colormap
2875
- self.ax.imshow(current_image,
2876
- alpha=0.7)
2877
- elif is_rgb and self.channel_data[channel].shape[-1] == 4:
2878
- self.ax.imshow(current_image) #For images that already have an alpha value and RGB, don't update alpha
2845
+ # Set axes limits before displaying any images
2846
+ self.ax.set_xlim(-0.5, min_width - 0.5)
2847
+ self.ax.set_ylim(min_height - 0.5, -0.5)
2848
+
2849
+ # Create subplot with tight layout and white figure background
2850
+ self.figure.patch.set_facecolor('white')
2851
+ self.ax = self.figure.add_subplot(111)
2852
+
2853
+ # Store current zoom limits if they exist and weren't provided
2854
+ if preserve_zoom is None and hasattr(self, 'ax'):
2855
+ current_xlim = self.ax.get_xlim() if self.ax.get_xlim() != (0, 1) else None
2856
+ current_ylim = self.ax.get_ylim() if self.ax.get_ylim() != (0, 1) else None
2857
+ else:
2858
+ current_xlim, current_ylim = preserve_zoom if preserve_zoom else (None, None)
2859
+
2860
+ # Define base colors for each channel with increased intensity
2861
+ base_colors = self.base_colors
2862
+ # Set only the axes (image area) background to black
2863
+ self.ax.set_facecolor('black')
2864
+
2865
+ # Display each visible channel
2866
+ for channel in range(4):
2867
+ if (self.channel_visible[channel] and
2868
+ self.channel_data[channel] is not None):
2869
+
2870
+ # Check if we're dealing with RGB data
2871
+ is_rgb = len(self.channel_data[channel].shape) == 4 and (self.channel_data[channel].shape[-1] == 3 or self.channel_data[channel].shape[-1] == 4)
2872
+
2873
+ if len(self.channel_data[channel].shape) == 3 and not is_rgb:
2874
+ current_image = self.channel_data[channel][self.current_slice, :, :]
2875
+ elif is_rgb:
2876
+ current_image = self.channel_data[channel][self.current_slice] # Already has RGB channels
2877
+ else:
2878
+ current_image = self.channel_data[channel]
2879
+
2880
+ if is_rgb and self.channel_data[channel].shape[-1] == 3:
2881
+ # For RGB images, just display directly without colormap
2882
+ self.ax.imshow(current_image,
2883
+ alpha=0.7)
2884
+ elif is_rgb and self.channel_data[channel].shape[-1] == 4:
2885
+ self.ax.imshow(current_image) #For images that already have an alpha value and RGB, don't update alpha
2879
2886
 
2887
+ else:
2888
+ # Regular channel processing with colormap
2889
+ # Calculate brightness/contrast limits from entire volume
2890
+ img_min = self.min_max[channel][0]
2891
+ img_max = self.min_max[channel][1]
2892
+
2893
+ # Calculate vmin and vmax, ensuring we don't get a zero range
2894
+ if img_min == img_max:
2895
+ vmin = img_min
2896
+ vmax = img_min + 1
2880
2897
  else:
2881
- # Regular channel processing with colormap
2882
- # Calculate brightness/contrast limits from entire volume
2883
- img_min = self.min_max[channel][0]
2884
- img_max = self.min_max[channel][1]
2885
-
2886
- # Calculate vmin and vmax, ensuring we don't get a zero range
2887
- if img_min == img_max:
2888
- vmin = img_min
2889
- vmax = img_min + 1
2890
- else:
2891
- vmin = img_min + (img_max - img_min) * self.channel_brightness[channel]['min']
2892
- vmax = img_min + (img_max - img_min) * self.channel_brightness[channel]['max']
2893
-
2894
- # Normalize the image safely
2895
- if vmin == vmax:
2896
- normalized_image = np.zeros_like(current_image)
2897
- else:
2898
- normalized_image = np.clip((current_image - vmin) / (vmax - vmin), 0, 1)
2899
-
2900
- # Create custom colormap with higher intensity
2901
- color = base_colors[channel]
2902
- custom_cmap = LinearSegmentedColormap.from_list(
2903
- f'custom_{channel}',
2904
- [(0,0,0,0), (*color,1)]
2905
- )
2906
-
2907
- # Display the image with slightly higher alpha
2908
- self.ax.imshow(normalized_image,
2909
- alpha=0.7,
2910
- cmap=custom_cmap,
2911
- vmin=0,
2912
- vmax=1)
2913
-
2914
- # Rest of the code remains the same...
2915
- # Add highlight overlay if it exists
2916
- if self.highlight_overlay is not None:
2917
- highlight_slice = self.highlight_overlay[self.current_slice]
2918
- highlight_cmap = LinearSegmentedColormap.from_list(
2919
- 'highlight',
2920
- [(0, 0, 0, 0), (1, 1, 0, 1)] # yellow
2921
- )
2922
- self.ax.imshow(highlight_slice,
2923
- cmap=highlight_cmap,
2924
- alpha=0.5)
2925
-
2926
- # Restore zoom limits if they existed
2927
- if current_xlim is not None and current_ylim is not None:
2928
- self.ax.set_xlim(current_xlim)
2929
- self.ax.set_ylim(current_ylim)
2930
-
2931
- # Style the axes
2932
- self.ax.set_xlabel('X')
2933
- self.ax.set_ylabel('Y')
2934
- self.ax.set_title(f'Slice {self.current_slice}')
2935
-
2936
- # Make axis labels and ticks black for visibility against white background
2937
- self.ax.xaxis.label.set_color('black')
2938
- self.ax.yaxis.label.set_color('black')
2939
- self.ax.title.set_color('black')
2940
- self.ax.tick_params(colors='black')
2941
- for spine in self.ax.spines.values():
2942
- spine.set_color('black')
2943
-
2944
- # Adjust the layout to ensure the plot fits well in the figure
2945
- self.figure.tight_layout()
2946
-
2947
- # Redraw measurement points and their labels
2948
- for point in self.measurement_points:
2949
- x1, y1, z1 = point['point1']
2950
- x2, y2, z2 = point['point2']
2951
- pair_idx = point['pair_index']
2952
-
2953
- # Draw points and labels if they're on current slice
2954
- if z1 == self.current_slice:
2955
- self.ax.plot(x1, y1, 'yo', markersize=8)
2956
- self.ax.text(x1, y1+5, str(pair_idx),
2957
- color='white', ha='center', va='bottom')
2958
- if z2 == self.current_slice:
2959
- self.ax.plot(x2, y2, 'yo', markersize=8)
2960
- self.ax.text(x2, y2+5, str(pair_idx),
2961
- color='white', ha='center', va='bottom')
2898
+ vmin = img_min + (img_max - img_min) * self.channel_brightness[channel]['min']
2899
+ vmax = img_min + (img_max - img_min) * self.channel_brightness[channel]['max']
2962
2900
 
2963
- # Draw line if both points are on current slice
2964
- if z1 == z2 == self.current_slice:
2965
- self.ax.plot([x1, x2], [y1, y2], 'r--', alpha=0.5)
2901
+ # Normalize the image safely
2902
+ if vmin == vmax:
2903
+ normalized_image = np.zeros_like(current_image)
2904
+ else:
2905
+ normalized_image = np.clip((current_image - vmin) / (vmax - vmin), 0, 1)
2906
+
2907
+ # Create custom colormap with higher intensity
2908
+ color = base_colors[channel]
2909
+ custom_cmap = LinearSegmentedColormap.from_list(
2910
+ f'custom_{channel}',
2911
+ [(0,0,0,0), (*color,1)]
2912
+ )
2913
+
2914
+ # Display the image with slightly higher alpha
2915
+ self.ax.imshow(normalized_image,
2916
+ alpha=0.7,
2917
+ cmap=custom_cmap,
2918
+ vmin=0,
2919
+ vmax=1,
2920
+ extent=(-0.5, min_width-0.5, min_height-0.5, -0.5))
2921
+
2922
+ # Add highlight overlay if it exists
2923
+ if self.highlight_overlay is not None:
2924
+ highlight_slice = self.highlight_overlay[self.current_slice]
2925
+ highlight_cmap = LinearSegmentedColormap.from_list(
2926
+ 'highlight',
2927
+ [(0, 0, 0, 0), (1, 1, 0, 1)] # yellow
2928
+ )
2929
+ self.ax.imshow(highlight_slice,
2930
+ cmap=highlight_cmap,
2931
+ alpha=0.5)
2932
+
2933
+ # Restore zoom limits if they existed
2934
+ if current_xlim is not None and current_ylim is not None:
2935
+ self.ax.set_xlim(current_xlim)
2936
+ self.ax.set_ylim(current_ylim)
2937
+
2938
+ # Style the axes
2939
+ self.ax.set_xlabel('X')
2940
+ self.ax.set_ylabel('Y')
2941
+ self.ax.set_title(f'Slice {self.current_slice}')
2942
+
2943
+ # Make axis labels and ticks black for visibility against white background
2944
+ self.ax.xaxis.label.set_color('black')
2945
+ self.ax.yaxis.label.set_color('black')
2946
+ self.ax.title.set_color('black')
2947
+ self.ax.tick_params(colors='black')
2948
+ for spine in self.ax.spines.values():
2949
+ spine.set_color('black')
2950
+
2951
+ # Adjust the layout to ensure the plot fits well in the figure
2952
+ self.figure.tight_layout()
2953
+
2954
+ # Redraw measurement points and their labels
2955
+ for point in self.measurement_points:
2956
+ x1, y1, z1 = point['point1']
2957
+ x2, y2, z2 = point['point2']
2958
+ pair_idx = point['pair_index']
2959
+
2960
+ # Draw points and labels if they're on current slice
2961
+ if z1 == self.current_slice:
2962
+ self.ax.plot(x1, y1, 'yo', markersize=8)
2963
+ self.ax.text(x1, y1+5, str(pair_idx),
2964
+ color='white', ha='center', va='bottom')
2965
+ if z2 == self.current_slice:
2966
+ self.ax.plot(x2, y2, 'yo', markersize=8)
2967
+ self.ax.text(x2, y2+5, str(pair_idx),
2968
+ color='white', ha='center', va='bottom')
2969
+
2970
+ # Draw line if both points are on current slice
2971
+ if z1 == z2 == self.current_slice:
2972
+ self.ax.plot([x1, x2], [y1, y2], 'r--', alpha=0.5)
2973
+
2974
+ if active_channels:
2975
+ self.ax.set_xlim(-0.5, min_width - 0.5)
2976
+ self.ax.set_ylim(min_height - 0.5, -0.5)
2966
2977
 
2967
- self.canvas.draw()
2968
-
2978
+ self.canvas.draw()
2969
2979
  def show_netshow_dialog(self):
2970
2980
  dialog = NetShowDialog(self)
2971
2981
  dialog.exec()
@@ -4969,6 +4979,7 @@ class ResizeDialog(QDialog):
4969
4979
  resized_data = n3d.resize(self.parent().channel_data[channel], resize, order)
4970
4980
  self.parent().load_channel(channel, channel_data=resized_data, data=True, assign_shape = False)
4971
4981
 
4982
+
4972
4983
 
4973
4984
  # Process highlight overlay if it exists
4974
4985
  if self.parent().highlight_overlay is not None:
@@ -4990,8 +5001,8 @@ class ResizeDialog(QDialog):
4990
5001
  # Process highlight overlay if it exists
4991
5002
  if self.parent().highlight_overlay is not None:
4992
5003
  self.parent().highlight_overlay = n3d.upsample_with_padding(self.parent().highlight_overlay, original_shape = self.parent().original_shape)
4993
-
4994
- my_network.search_region = n3d.upsample_with_padding(my_network.search_region, original_shape = self.parent().original_shape)
5004
+ if my_network.search_region is not None:
5005
+ my_network.search_region = n3d.upsample_with_padding(my_network.search_region, original_shape = self.parent().original_shape)
4995
5006
 
4996
5007
 
4997
5008
  # Update slider range based on new z-dimension
@@ -5417,7 +5428,7 @@ class DilateDialog(QDialog):
5417
5428
  )
5418
5429
 
5419
5430
  # Update both the display data and the network object
5420
- self.parent().load_channel(self.parent().active_channel, result, True, preserve_zoom=(current_xlim, current_ylim))
5431
+ self.parent().load_channel(self.parent().active_channel, result, True)
5421
5432
 
5422
5433
  self.parent().update_display(preserve_zoom=(current_xlim, current_ylim))
5423
5434
  self.accept()
@@ -5503,7 +5514,7 @@ class ErodeDialog(QDialog):
5503
5514
  )
5504
5515
 
5505
5516
 
5506
- self.parent().load_channel(self.parent().active_channel, result, True, preserve_zoom=(current_xlim, current_ylim))
5517
+ self.parent().load_channel(self.parent().active_channel, result, True)
5507
5518
 
5508
5519
 
5509
5520
  self.parent().update_display(preserve_zoom=(current_xlim, current_ylim))
@@ -5908,8 +5919,13 @@ class GenNodesDialog(QDialog):
5908
5919
  if down_factor is None:
5909
5920
  self.down_factor = QLineEdit("0")
5910
5921
  layout.addRow("Downsample Factor (Speeds up calculation at the cost of fidelity):", self.down_factor)
5922
+ self.cubic = QPushButton("Cubic Downsample")
5923
+ self.cubic.setCheckable(True)
5924
+ self.cubic.setChecked(False)
5925
+ layout.addRow("(if downsampling): Use cubic dilation? (Slower but can preserve structure better)", self.cubic)
5911
5926
  else:
5912
- self.down_factor = down_factor
5927
+ self.down_factor = down_factor[0]
5928
+ self.cubic = down_factor[1]
5913
5929
 
5914
5930
  self.directory = QLineEdit()
5915
5931
  self.directory.setPlaceholderText("Leave empty to save in active dir")
@@ -5922,7 +5938,7 @@ class GenNodesDialog(QDialog):
5922
5938
  self.retain.setChecked(True)
5923
5939
  layout.addRow("Retain Original Edges? (Will be moved to overlay 2):", self.retain)
5924
5940
  else:
5925
- self.retain = True
5941
+ self.retain = False
5926
5942
 
5927
5943
  # Add Run button
5928
5944
  run_button = QPushButton("Run Node Generation")
@@ -5956,17 +5972,24 @@ class GenNodesDialog(QDialog):
5956
5972
  # Get down_factor
5957
5973
  if type(self.down_factor) is int:
5958
5974
  down_factor = self.down_factor
5975
+ cubic = self.cubic
5959
5976
  else:
5960
5977
  try:
5961
5978
  down_factor = int(self.down_factor.text()) if self.down_factor.text() else 0
5962
5979
  except ValueError:
5963
5980
  down_factor = 0
5981
+ cubic = self.cubic.isChecked()
5964
5982
 
5965
5983
  try:
5966
5984
  retain = self.retain.isChecked()
5967
5985
  except:
5968
5986
  retain = True
5969
5987
 
5988
+ if cubic:
5989
+ order = 3
5990
+ else:
5991
+ order = 0
5992
+
5970
5993
 
5971
5994
  result, skele = n3d.label_vertices(
5972
5995
  my_network.edges,
@@ -5974,12 +5997,14 @@ class GenNodesDialog(QDialog):
5974
5997
  branch_removal=branch_removal,
5975
5998
  comp_dil=comp_dil,
5976
5999
  down_factor=down_factor,
6000
+ order = order,
5977
6001
  return_skele = True
5978
6002
 
5979
6003
  )
5980
6004
 
5981
6005
  if down_factor > 0 and not self.called:
5982
- my_network.edges = n3d.downsample(my_network.edges, down_factor)
6006
+
6007
+ my_network.edges = n3d.downsample(my_network.edges, down_factor, order = order)
5983
6008
  my_network.xy_scale = my_network.xy_scale * down_factor
5984
6009
  my_network.z_scale = my_network.z_scale * down_factor
5985
6010
  print("xy_scales and z_scales have been adjusted per downsample. Check image -> properties to manually reset them to 1 if desired.")
@@ -5995,13 +6020,14 @@ class GenNodesDialog(QDialog):
5995
6020
  except:
5996
6021
  pass
5997
6022
 
6023
+ self.parent().load_channel(1, channel_data = skele, data = True)
6024
+
6025
+ self.parent().load_channel(0, channel_data = result, data = True)
5998
6026
 
5999
6027
  if retain:
6000
6028
  self.parent().load_channel(3, channel_data = my_network.edges, data = True)
6001
6029
 
6002
- self.parent().load_channel(1, channel_data = skele, data = True)
6003
6030
 
6004
- self.parent().load_channel(0, channel_data = result, data = True)
6005
6031
 
6006
6032
  self.parent().update_display()
6007
6033
  self.accept()
@@ -6040,6 +6066,12 @@ class BranchDialog(QDialog):
6040
6066
  self.down_factor = QLineEdit("0")
6041
6067
  layout.addRow("Internal downsample (will have to recompute nodes)?:", self.down_factor)
6042
6068
 
6069
+ # cubic checkbox (default False)
6070
+ self.cubic = QPushButton("Cubic Downsample")
6071
+ self.cubic.setCheckable(True)
6072
+ self.cubic.setChecked(False)
6073
+ layout.addRow("(if downsampling): Use cubic dilation? (Slower but can preserve structure better)", self.cubic)
6074
+
6043
6075
  # Add Run button
6044
6076
  run_button = QPushButton("Run Branch Label")
6045
6077
  run_button.clicked.connect(self.branch_label)
@@ -6056,25 +6088,29 @@ class BranchDialog(QDialog):
6056
6088
 
6057
6089
  nodes = self.nodes.isChecked()
6058
6090
  GPU = self.GPU.isChecked()
6091
+ cubic = self.cubic.isChecked()
6092
+
6059
6093
 
6060
6094
  original_shape = my_network.edges.shape
6095
+ original_array = copy.deepcopy(my_network.edges)
6061
6096
 
6062
6097
  if down_factor > 0:
6063
- self.parent().show_gennodes_dialog(down_factor = down_factor, called = True)
6098
+ self.parent().show_gennodes_dialog(down_factor = [down_factor, cubic], called = True)
6064
6099
  elif nodes:
6065
6100
  self.parent().show_gennodes_dialog(called = True)
6066
6101
  down_factor = None
6067
6102
 
6068
6103
  if my_network.edges is not None and my_network.nodes is not None and my_network.id_overlay is not None:
6069
6104
 
6070
- output = n3d.label_branches(my_network.edges, nodes = my_network.nodes, bonus_array = my_network.id_overlay, GPU = GPU, down_factor = down_factor)
6105
+ output = n3d.label_branches(my_network.edges, nodes = my_network.nodes, bonus_array = original_array, GPU = GPU, down_factor = down_factor, arrayshape = original_shape)
6071
6106
 
6072
6107
  if down_factor is not None:
6073
6108
 
6074
- self.parent().reset(nodes = True, id_overlay = True)
6109
+ self.parent().reset(nodes = True, id_overlay = True, edges = True)
6075
6110
 
6076
6111
  else:
6077
6112
  self.parent().reset(id_overlay = True)
6113
+ self.parent().update_display(dims = (output.shape[1], output.shape[2]))
6078
6114
 
6079
6115
  self.parent().load_channel(1, channel_data = output, data = True)
6080
6116
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: nettracer3d
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
5
5
  Author-email: Liam McLaughlin <boom2449@gmail.com>
6
6
  Project-URL: User_Manual, https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link
File without changes
File without changes
File without changes