nettracer3d 1.2.5__py3-none-any.whl → 1.2.7__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/branch_stitcher.py +9 -4
- nettracer3d/filaments.py +11 -4
- nettracer3d/modularity.py +15 -6
- nettracer3d/morphology.py +1 -1
- nettracer3d/nettracer.py +53 -155
- nettracer3d/nettracer_gui.py +218 -127
- nettracer3d/network_analysis.py +36 -48
- nettracer3d/network_draw.py +16 -15
- nettracer3d/node_draw.py +4 -4
- nettracer3d/proximity.py +36 -150
- nettracer3d/simple_network.py +28 -9
- nettracer3d/smart_dilate.py +200 -107
- nettracer3d/tutorial.py +32 -65
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.2.7.dist-info}/METADATA +19 -7
- nettracer3d-1.2.7.dist-info/RECORD +29 -0
- nettracer3d-1.2.5.dist-info/RECORD +0 -29
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.2.7.dist-info}/WHEEL +0 -0
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.2.7.dist-info}/entry_points.txt +0 -0
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.2.7.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.2.7.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -2565,10 +2565,40 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2565
2565
|
# Apply pass 2 results
|
|
2566
2566
|
pass2_array = pass1_array.copy()
|
|
2567
2567
|
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2568
|
+
def replace_labels_in_chunk(args):
|
|
2569
|
+
"""Process a single chunk of the array"""
|
|
2570
|
+
pass1_chunk, results_pass2 = args
|
|
2571
|
+
|
|
2572
|
+
# Create output chunk (copy of input)
|
|
2573
|
+
pass2_chunk = pass1_chunk.copy()
|
|
2574
|
+
|
|
2575
|
+
# Find which labels actually exist in this chunk
|
|
2576
|
+
unique_labels = set(np.unique(pass1_chunk))
|
|
2577
|
+
|
|
2578
|
+
for illegal_label, new_label in results_pass2:
|
|
2579
|
+
if new_label is not None and new_label != illegal_label:
|
|
2580
|
+
# Only process if this label exists in this chunk
|
|
2581
|
+
if illegal_label in unique_labels:
|
|
2582
|
+
# Read from pass1_chunk, write to pass2_chunk
|
|
2583
|
+
pass2_chunk[pass1_chunk == illegal_label] = new_label
|
|
2584
|
+
|
|
2585
|
+
return pass2_chunk
|
|
2586
|
+
|
|
2587
|
+
# Get number of CPU cores
|
|
2588
|
+
num_cores = mp.cpu_count()
|
|
2589
|
+
|
|
2590
|
+
# Split array along y-axis (axis=1)
|
|
2591
|
+
chunks = np.array_split(pass1_array, num_cores, axis=1)
|
|
2592
|
+
|
|
2593
|
+
# Prepare arguments for each worker
|
|
2594
|
+
chunk_args = [(chunk, results_pass2) for chunk in chunks]
|
|
2595
|
+
|
|
2596
|
+
# Process chunks in parallel with threads
|
|
2597
|
+
with ThreadPoolExecutor(max_workers=num_cores) as executor:
|
|
2598
|
+
processed_chunks = list(executor.map(replace_labels_in_chunk, chunk_args))
|
|
2599
|
+
|
|
2600
|
+
# Stack results back together along y-axis
|
|
2601
|
+
pass2_array = np.concatenate(processed_chunks, axis=1)
|
|
2572
2602
|
|
|
2573
2603
|
print(f"Pass 2 complete")
|
|
2574
2604
|
return pass2_array
|
|
@@ -4927,6 +4957,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4927
4957
|
documentation_action.triggered.connect(self.help_me)
|
|
4928
4958
|
tutorial_action = help_menu.addAction("Tutorial")
|
|
4929
4959
|
tutorial_action.triggered.connect(self.start_tutorial)
|
|
4960
|
+
documentation_action = help_menu.addAction("Youtube")
|
|
4961
|
+
documentation_action.triggered.connect(self.help_me_vid)
|
|
4930
4962
|
|
|
4931
4963
|
# Initialize downsample factor
|
|
4932
4964
|
self.downsample_factor = 1
|
|
@@ -5189,6 +5221,16 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5189
5221
|
print(f"Error opening URL: {e}")
|
|
5190
5222
|
return False
|
|
5191
5223
|
|
|
5224
|
+
def help_me_vid(self):
|
|
5225
|
+
|
|
5226
|
+
import webbrowser
|
|
5227
|
+
try:
|
|
5228
|
+
webbrowser.open('https://www.youtube.com/watch?v=_4uDy0mzG94&list=PLsrhxiimzKJMZ3_gTWkfrcAdJQQobUhj7')
|
|
5229
|
+
return True
|
|
5230
|
+
except Exception as e:
|
|
5231
|
+
print(f"Error opening URL: {e}")
|
|
5232
|
+
return False
|
|
5233
|
+
|
|
5192
5234
|
def start_tutorial(self):
|
|
5193
5235
|
"""Open the tutorial selection dialog"""
|
|
5194
5236
|
if not hasattr(self, 'tutorial_dialog'):
|
|
@@ -5588,10 +5630,13 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5588
5630
|
except:
|
|
5589
5631
|
pass
|
|
5590
5632
|
|
|
5591
|
-
def show_dilate_dialog(self, args = None):
|
|
5633
|
+
def show_dilate_dialog(self, args = None, execute = False):
|
|
5592
5634
|
"""show the dilate dialog"""
|
|
5593
5635
|
dialog = DilateDialog(self, args)
|
|
5594
|
-
|
|
5636
|
+
if not execute:
|
|
5637
|
+
dialog.show()
|
|
5638
|
+
else:
|
|
5639
|
+
dialog.exec()
|
|
5595
5640
|
|
|
5596
5641
|
def show_erode_dialog(self, args = None):
|
|
5597
5642
|
"""show the erode dialog"""
|
|
@@ -6406,12 +6451,15 @@ class ImageViewerWindow(QMainWindow):
|
|
|
6406
6451
|
self.channel_data[channel_index] = test_channel_data
|
|
6407
6452
|
|
|
6408
6453
|
elif file_extension == 'nii':
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6454
|
+
try:
|
|
6455
|
+
import nibabel as nib
|
|
6456
|
+
nii_img = nib.load(filename)
|
|
6457
|
+
# Get data and transpose to match TIFF orientation
|
|
6458
|
+
# If X needs to become Z, we move axis 2 (X) to position 0 (Z)
|
|
6459
|
+
arraydata = nii_img.get_fdata()
|
|
6460
|
+
self.channel_data[channel_index] = np.transpose(arraydata, (2, 1, 0))
|
|
6461
|
+
except:
|
|
6462
|
+
return
|
|
6415
6463
|
|
|
6416
6464
|
elif file_extension in ['jpg', 'jpeg', 'png']:
|
|
6417
6465
|
from PIL import Image
|
|
@@ -6737,6 +6785,12 @@ class ImageViewerWindow(QMainWindow):
|
|
|
6737
6785
|
|
|
6738
6786
|
else: # Save
|
|
6739
6787
|
parent_dir = None # Let the backend handle default save location
|
|
6788
|
+
|
|
6789
|
+
if len(self.channel_data[0].shape) == 4:
|
|
6790
|
+
try:
|
|
6791
|
+
self.load_channel(0, self.reduce_rgb_dimension(self.channel_data[0], 'weight'), True)
|
|
6792
|
+
except:
|
|
6793
|
+
pass
|
|
6740
6794
|
|
|
6741
6795
|
# Call appropriate save method
|
|
6742
6796
|
if asbool:
|
|
@@ -8733,6 +8787,12 @@ class MergeNodeIdDialog(QDialog):
|
|
|
8733
8787
|
layout.addRow("xy_scale:", self.xy_scale)
|
|
8734
8788
|
self.z_scale = QLineEdit(f"{my_network.z_scale}")
|
|
8735
8789
|
layout.addRow("z_scale:", self.z_scale)
|
|
8790
|
+
|
|
8791
|
+
self.search_mode = QComboBox()
|
|
8792
|
+
self.search_mode.addItems(["Standard", "Fast (May be Rougher at Handling Adjacent Expanded Borders - Use for Very Large 3D Images)"])
|
|
8793
|
+
self.search_mode.setCurrentIndex(0) # Default to Mode 1
|
|
8794
|
+
layout.addRow("Step-out algorithm:", self.search_mode)
|
|
8795
|
+
|
|
8736
8796
|
self.mode_selector = QComboBox()
|
|
8737
8797
|
self.mode_selector.addItems(["Auto-Binarize(Otsu)/Presegmented", "Manual (Interactive Thresholder)"])
|
|
8738
8798
|
self.mode_selector.setCurrentIndex(1) # Default to Mode 1
|
|
@@ -8794,6 +8854,12 @@ class MergeNodeIdDialog(QDialog):
|
|
|
8794
8854
|
data = self.parent().channel_data[0]
|
|
8795
8855
|
include = self.include.isChecked()
|
|
8796
8856
|
umap = True
|
|
8857
|
+
search_mode = self.search_mode.currentIndex()
|
|
8858
|
+
|
|
8859
|
+
if search_mode == 0:
|
|
8860
|
+
fast_dil = False
|
|
8861
|
+
else:
|
|
8862
|
+
fast_dil = True
|
|
8797
8863
|
|
|
8798
8864
|
if data is None:
|
|
8799
8865
|
return
|
|
@@ -8810,7 +8876,7 @@ class MergeNodeIdDialog(QDialog):
|
|
|
8810
8876
|
return # User cancelled directory selection
|
|
8811
8877
|
|
|
8812
8878
|
if search > 0:
|
|
8813
|
-
data = sdl.smart_dilate(data,
|
|
8879
|
+
data = sdl.smart_dilate(data, fast_dil=fast_dil,
|
|
8814
8880
|
use_dt_dil_amount=search, xy_scale=xy_scale, z_scale=z_scale)
|
|
8815
8881
|
|
|
8816
8882
|
# Check if manual mode is selected
|
|
@@ -9421,6 +9487,10 @@ class NetShowDialog(QDialog):
|
|
|
9421
9487
|
self.geo_layout.setCheckable(True)
|
|
9422
9488
|
self.geo_layout.setChecked(False)
|
|
9423
9489
|
layout.addRow("Use Geographic Layout:", self.geo_layout)
|
|
9490
|
+
|
|
9491
|
+
self.show_labels = QCheckBox("Show Node Numerical IDs?")
|
|
9492
|
+
self.show_labels.setChecked(True)
|
|
9493
|
+
layout.addRow(self.show_labels)
|
|
9424
9494
|
|
|
9425
9495
|
# Add mode selection dropdown
|
|
9426
9496
|
self.mode_selector = QComboBox()
|
|
@@ -9442,6 +9512,8 @@ class NetShowDialog(QDialog):
|
|
|
9442
9512
|
def show_network(self):
|
|
9443
9513
|
# Get parameters and run analysis
|
|
9444
9514
|
|
|
9515
|
+
show_labels = self.show_labels.isChecked()
|
|
9516
|
+
|
|
9445
9517
|
geo = self.geo_layout.isChecked()
|
|
9446
9518
|
if geo:
|
|
9447
9519
|
if my_network.node_centroids is None:
|
|
@@ -9461,12 +9533,12 @@ class NetShowDialog(QDialog):
|
|
|
9461
9533
|
|
|
9462
9534
|
try:
|
|
9463
9535
|
if accepted_mode == 0:
|
|
9464
|
-
my_network.show_network(geometric=geo,
|
|
9536
|
+
my_network.show_network(geometric=geo, show_labels = show_labels)
|
|
9465
9537
|
elif accepted_mode == 1:
|
|
9466
|
-
my_network.show_communities_flex(geometric=geo,
|
|
9538
|
+
my_network.show_communities_flex(geometric=geo, weighted = weighted, partition = my_network.communities, show_labels = show_labels)
|
|
9467
9539
|
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID')
|
|
9468
9540
|
elif accepted_mode == 2:
|
|
9469
|
-
my_network.show_identity_network(geometric=geo,
|
|
9541
|
+
my_network.show_identity_network(geometric=geo, show_labels = show_labels)
|
|
9470
9542
|
|
|
9471
9543
|
self.accept()
|
|
9472
9544
|
except Exception as e:
|
|
@@ -10099,8 +10171,8 @@ class NeighborIdentityDialog(QDialog):
|
|
|
10099
10171
|
|
|
10100
10172
|
self.fastdil = QPushButton("Fast Dilate")
|
|
10101
10173
|
self.fastdil.setCheckable(True)
|
|
10102
|
-
self.fastdil.setChecked(
|
|
10103
|
-
|
|
10174
|
+
self.fastdil.setChecked(True)
|
|
10175
|
+
layout.addRow("(If not using network) Use Fast Dilation (Parallelized):", self.fastdil)
|
|
10104
10176
|
|
|
10105
10177
|
# Add Run button
|
|
10106
10178
|
run_button = QPushButton("Get Neighborhood Identity Distribution")
|
|
@@ -11776,17 +11848,21 @@ class CleanDialog(QDialog):
|
|
|
11776
11848
|
def close(self):
|
|
11777
11849
|
|
|
11778
11850
|
try:
|
|
11779
|
-
self.parent().show_dilate_dialog(args = [1])
|
|
11780
|
-
self.parent().show_erode_dialog(args =
|
|
11851
|
+
self.parent().show_dilate_dialog(args = [1, 0], execute = True)
|
|
11852
|
+
self.parent().show_erode_dialog(args = self.parent().last_dil)
|
|
11781
11853
|
except:
|
|
11854
|
+
import traceback
|
|
11855
|
+
print(traceback.format_exc())
|
|
11782
11856
|
pass
|
|
11783
11857
|
|
|
11784
11858
|
def open(self):
|
|
11785
11859
|
|
|
11786
11860
|
try:
|
|
11787
|
-
self.parent().show_erode_dialog(args = [1])
|
|
11788
|
-
self.parent().show_dilate_dialog(args =
|
|
11861
|
+
self.parent().show_erode_dialog(args = [1, 0])
|
|
11862
|
+
self.parent().show_dilate_dialog(args = self.parent().last_ero, execute = True)
|
|
11789
11863
|
except:
|
|
11864
|
+
import traceback
|
|
11865
|
+
print(traceback.format_exc())
|
|
11790
11866
|
pass
|
|
11791
11867
|
|
|
11792
11868
|
def holes(self):
|
|
@@ -13051,8 +13127,7 @@ class MachineWindow(QMainWindow):
|
|
|
13051
13127
|
self.kill_segmentation()
|
|
13052
13128
|
time.sleep(0.2) # Give additional time for cleanup
|
|
13053
13129
|
try:
|
|
13054
|
-
self.parent().
|
|
13055
|
-
self.update_display()
|
|
13130
|
+
self.parent().load_channel(0, self.parent().reduce_rgb_dimension(self.parent().channel_data[0], 'weight'), True)
|
|
13056
13131
|
except:
|
|
13057
13132
|
pass
|
|
13058
13133
|
|
|
@@ -13613,20 +13688,11 @@ class SmartDilateDialog(QDialog):
|
|
|
13613
13688
|
|
|
13614
13689
|
layout = QFormLayout(self)
|
|
13615
13690
|
|
|
13616
|
-
# GPU checkbox (default True)
|
|
13617
|
-
self.GPU = QPushButton("GPU")
|
|
13618
|
-
self.GPU.setCheckable(True)
|
|
13619
|
-
self.GPU.setChecked(False)
|
|
13620
|
-
layout.addRow("Use GPU:", self.GPU)
|
|
13621
|
-
|
|
13622
13691
|
# dt checkbox (default False)
|
|
13623
13692
|
self.predt = QPushButton("Fast Dilation")
|
|
13624
13693
|
self.predt.setCheckable(True)
|
|
13625
13694
|
self.predt.setChecked(False)
|
|
13626
|
-
layout.addRow("Use Fast Dilation (Higher speed,
|
|
13627
|
-
|
|
13628
|
-
self.down_factor = QLineEdit("")
|
|
13629
|
-
layout.addRow("Internal Downsample for GPU (if needed):", self.down_factor)
|
|
13695
|
+
layout.addRow("Use Fast Dilation? (Higher speed, may be rougher along adjacent boundaries):", self.predt)
|
|
13630
13696
|
|
|
13631
13697
|
self.params = params
|
|
13632
13698
|
|
|
@@ -13637,14 +13703,10 @@ class SmartDilateDialog(QDialog):
|
|
|
13637
13703
|
|
|
13638
13704
|
def smart_dilate(self):
|
|
13639
13705
|
|
|
13640
|
-
GPU = self.GPU.isChecked()
|
|
13641
|
-
down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
|
|
13642
13706
|
predt = self.predt.isChecked()
|
|
13643
13707
|
active_data, amount, xy_scale, z_scale = self.params
|
|
13644
13708
|
|
|
13645
|
-
|
|
13646
|
-
|
|
13647
|
-
result = sdl.smart_dilate(active_data, dilate_xy, dilate_z, GPU = GPU, predownsample = down_factor, fast_dil = predt, use_dt_dil_amount = amount, xy_scale = xy_scale, z_scale = z_scale)
|
|
13709
|
+
result = sdl.smart_dilate(active_data, fast_dil = predt, use_dt_dil_amount = amount, xy_scale = xy_scale, z_scale = z_scale)
|
|
13648
13710
|
|
|
13649
13711
|
self.parent().load_channel(self.parent().active_channel, result, True, preserve_zoom = (self.parent().ax.get_xlim(), self.parent().ax.get_ylim()))
|
|
13650
13712
|
self.accept()
|
|
@@ -13661,7 +13723,10 @@ class DilateDialog(QDialog):
|
|
|
13661
13723
|
|
|
13662
13724
|
if args:
|
|
13663
13725
|
self.parent().last_dil = args[0]
|
|
13664
|
-
|
|
13726
|
+
if args[1] > 1:
|
|
13727
|
+
self.index = 1
|
|
13728
|
+
else:
|
|
13729
|
+
self.index = 0
|
|
13665
13730
|
else:
|
|
13666
13731
|
self.parent().last_dil = 1
|
|
13667
13732
|
self.index = 0
|
|
@@ -13677,7 +13742,7 @@ class DilateDialog(QDialog):
|
|
|
13677
13742
|
|
|
13678
13743
|
# Add mode selection dropdown
|
|
13679
13744
|
self.mode_selector = QComboBox()
|
|
13680
|
-
self.mode_selector.addItems(["Distance Transform-Based
|
|
13745
|
+
self.mode_selector.addItems(["Parallel Distance Transform-Based", "Preserve Labels (Distance Transform Based)", "Pseudo3D Binary Kernels (For Fast, small dilations for visualization purposes. Slightly inaccurate, moreso at large dilations)", "Distance Transform-Based (Non-Parallel Version)"])
|
|
13681
13746
|
self.mode_selector.setCurrentIndex(self.index) # Default to Mode 1
|
|
13682
13747
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
13683
13748
|
|
|
@@ -13720,7 +13785,7 @@ class DilateDialog(QDialog):
|
|
|
13720
13785
|
if active_data is None:
|
|
13721
13786
|
raise ValueError("No active image selected")
|
|
13722
13787
|
|
|
13723
|
-
self.parent().last_dil = amount
|
|
13788
|
+
self.parent().last_dil = [amount, accepted_mode]
|
|
13724
13789
|
|
|
13725
13790
|
if accepted_mode == 1:
|
|
13726
13791
|
dialog = SmartDilateDialog(self.parent(), [active_data, amount, xy_scale, z_scale])
|
|
@@ -13729,6 +13794,8 @@ class DilateDialog(QDialog):
|
|
|
13729
13794
|
return
|
|
13730
13795
|
|
|
13731
13796
|
if accepted_mode == 0:
|
|
13797
|
+
result = n3d.dilate_3D_dt(active_data, amount, xy_scaling = xy_scale, z_scaling = z_scale, fast_dil = True)
|
|
13798
|
+
elif accepted_mode == 3:
|
|
13732
13799
|
result = n3d.dilate_3D_dt(active_data, amount, xy_scaling = xy_scale, z_scaling = z_scale)
|
|
13733
13800
|
else:
|
|
13734
13801
|
|
|
@@ -13767,7 +13834,10 @@ class ErodeDialog(QDialog):
|
|
|
13767
13834
|
|
|
13768
13835
|
if args:
|
|
13769
13836
|
self.parent().last_ero = args[0]
|
|
13770
|
-
|
|
13837
|
+
if args[1] == 1: #user opted to preserve labels
|
|
13838
|
+
self.index = 2 #this is where the labels option lives in the erode menu
|
|
13839
|
+
else:
|
|
13840
|
+
self.index = 0
|
|
13771
13841
|
else:
|
|
13772
13842
|
self.parent().last_ero = 1
|
|
13773
13843
|
self.index = 0
|
|
@@ -13783,7 +13853,7 @@ class ErodeDialog(QDialog):
|
|
|
13783
13853
|
|
|
13784
13854
|
# Add mode selection dropdown
|
|
13785
13855
|
self.mode_selector = QComboBox()
|
|
13786
|
-
self.mode_selector.addItems(["Distance Transform-Based (
|
|
13856
|
+
self.mode_selector.addItems(["Parallel Distance Transform Based", "Distance Transform-Based (Non-Parallel)", "Preserve Labels (Parallel)", "Preserve Labels (Non-Parallel)"])
|
|
13787
13857
|
self.mode_selector.setCurrentIndex(self.index) # Default to Mode 1
|
|
13788
13858
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
13789
13859
|
|
|
@@ -13820,7 +13890,7 @@ class ErodeDialog(QDialog):
|
|
|
13820
13890
|
|
|
13821
13891
|
mode = self.mode_selector.currentIndex()
|
|
13822
13892
|
|
|
13823
|
-
if mode ==
|
|
13893
|
+
if mode == 2 or mode == 3:
|
|
13824
13894
|
preserve_labels = True
|
|
13825
13895
|
else:
|
|
13826
13896
|
preserve_labels = False
|
|
@@ -13842,7 +13912,7 @@ class ErodeDialog(QDialog):
|
|
|
13842
13912
|
|
|
13843
13913
|
|
|
13844
13914
|
self.parent().load_channel(self.parent().active_channel, result, True, preserve_zoom = (self.parent().ax.get_xlim(), self.parent().ax.get_ylim()))
|
|
13845
|
-
self.parent().last_ero = amount
|
|
13915
|
+
self.parent().last_ero = [amount, mode]
|
|
13846
13916
|
self.accept()
|
|
13847
13917
|
|
|
13848
13918
|
except Exception as e:
|
|
@@ -14012,7 +14082,7 @@ class FilamentDialog(QDialog):
|
|
|
14012
14082
|
if downsample_factor and downsample_factor > 1:
|
|
14013
14083
|
data = n3d.downsample(data, downsample_factor)
|
|
14014
14084
|
|
|
14015
|
-
result = filaments.trace(data, kernel_spacing, max_distance, min_component, gap_tolerance, blob_sphericity, blob_volume, spine_removal, score_threshold)
|
|
14085
|
+
result = filaments.trace(data, kernel_spacing, max_distance, min_component, gap_tolerance, blob_sphericity, blob_volume, spine_removal, score_threshold, my_network.xy_scale, my_network.z_scale)
|
|
14016
14086
|
|
|
14017
14087
|
if downsample_factor and downsample_factor > 1:
|
|
14018
14088
|
|
|
@@ -14548,6 +14618,12 @@ class DistanceDialog(QDialog):
|
|
|
14548
14618
|
|
|
14549
14619
|
layout = QFormLayout(self)
|
|
14550
14620
|
|
|
14621
|
+
# Add mode selection dropdown
|
|
14622
|
+
self.mode_selector = QComboBox()
|
|
14623
|
+
self.mode_selector.addItems(["Parallel (Faster)", "Non-Parallel (Uses less CPU resources)"])
|
|
14624
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
14625
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
14626
|
+
|
|
14551
14627
|
# Add Run button
|
|
14552
14628
|
run_button = QPushButton("Run")
|
|
14553
14629
|
run_button.clicked.connect(self.run)
|
|
@@ -14557,12 +14633,20 @@ class DistanceDialog(QDialog):
|
|
|
14557
14633
|
|
|
14558
14634
|
try:
|
|
14559
14635
|
|
|
14636
|
+
mode = self.mode_selector.currentIndex()
|
|
14637
|
+
if mode == 0:
|
|
14638
|
+
fast_dil = True
|
|
14639
|
+
else:
|
|
14640
|
+
fast_dil = False
|
|
14641
|
+
|
|
14560
14642
|
data = self.parent().channel_data[self.parent().active_channel]
|
|
14561
14643
|
|
|
14562
|
-
data = sdl.compute_distance_transform_distance(data, sampling = [my_network.z_scale, my_network.xy_scale, my_network.xy_scale])
|
|
14644
|
+
data = sdl.compute_distance_transform_distance(data, sampling = [my_network.z_scale, my_network.xy_scale, my_network.xy_scale], fast_dil = fast_dil)
|
|
14563
14645
|
|
|
14564
14646
|
self.parent().load_channel(self.parent().active_channel, data, data = True, preserve_zoom = (self.parent().ax.get_xlim(), self.parent().ax.get_ylim()))
|
|
14565
14647
|
|
|
14648
|
+
self.accept()
|
|
14649
|
+
|
|
14566
14650
|
except Exception as e:
|
|
14567
14651
|
|
|
14568
14652
|
print(f"Error: {e}")
|
|
@@ -14680,17 +14764,24 @@ class WatershedDialog(QDialog):
|
|
|
14680
14764
|
self.proportion = QLineEdit(f"{self.default}")
|
|
14681
14765
|
layout.addRow(f"Proportion (0-1) of distance transform value set [ie unique elements] to exclude (ie 0.2 = 20% of the set of all values of the distance transform get excluded).\n Essentially, vals closer to 0 are less likely to split objects but also won't kick out small objects from the output, vals slightly further from 0 will split more aggressively, but vals closer to 1 become unstable, leading to objects being evicted or labelling errors. \nRecommend something between 0.05 and 0.4, but it depends on the data (Or just enter a smallest radius above to avoid using this). \nWill tell you in command window what equivalent 'smallest radius' this is):", self.proportion)
|
|
14682
14766
|
|
|
14767
|
+
|
|
14768
|
+
# Add mode selection dropdown
|
|
14769
|
+
self.mode_selector = QComboBox()
|
|
14770
|
+
self.mode_selector.addItems(["Parallel (Faster)", "Non-Parallel (Uses less CPU resources)"])
|
|
14771
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
14772
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
14773
|
+
|
|
14683
14774
|
# GPU checkbox (default True)
|
|
14684
14775
|
self.gpu = QPushButton("GPU")
|
|
14685
14776
|
self.gpu.setCheckable(True)
|
|
14686
14777
|
self.gpu.setChecked(False)
|
|
14687
|
-
layout.addRow("Use GPU:", self.gpu)
|
|
14778
|
+
#layout.addRow("Use GPU:", self.gpu)
|
|
14688
14779
|
|
|
14689
14780
|
|
|
14690
14781
|
# Predownsample (empty by default)
|
|
14691
14782
|
self.predownsample = QLineEdit()
|
|
14692
14783
|
self.predownsample.setPlaceholderText("Leave empty for None")
|
|
14693
|
-
layout.addRow("Kernel Obtainment GPU Downsample:", self.predownsample)
|
|
14784
|
+
#layout.addRow("Kernel Obtainment GPU Downsample:", self.predownsample)
|
|
14694
14785
|
|
|
14695
14786
|
# Predownsample2 (empty by default)
|
|
14696
14787
|
#self.predownsample2 = QLineEdit()
|
|
@@ -14707,6 +14798,14 @@ class WatershedDialog(QDialog):
|
|
|
14707
14798
|
|
|
14708
14799
|
def run_watershed(self):
|
|
14709
14800
|
try:
|
|
14801
|
+
|
|
14802
|
+
mode = self.mode_selector.currentIndex()
|
|
14803
|
+
|
|
14804
|
+
if mode == 0:
|
|
14805
|
+
fast_dil = True
|
|
14806
|
+
else:
|
|
14807
|
+
fast_dil = False
|
|
14808
|
+
|
|
14710
14809
|
# Get directory (None if empty)
|
|
14711
14810
|
directory = None
|
|
14712
14811
|
|
|
@@ -14750,6 +14849,7 @@ class WatershedDialog(QDialog):
|
|
|
14750
14849
|
proportion=proportion,
|
|
14751
14850
|
GPU=gpu,
|
|
14752
14851
|
smallest_rad=smallest_rad,
|
|
14852
|
+
fast_dil = fast_dil,
|
|
14753
14853
|
predownsample=predownsample,
|
|
14754
14854
|
predownsample2=predownsample2
|
|
14755
14855
|
)
|
|
@@ -14958,53 +15058,10 @@ class GenNodesDialog(QDialog):
|
|
|
14958
15058
|
main_layout = QVBoxLayout(self)
|
|
14959
15059
|
self.called = called
|
|
14960
15060
|
|
|
14961
|
-
# Set down_factor
|
|
15061
|
+
# Set down_factor
|
|
14962
15062
|
if not down_factor:
|
|
14963
15063
|
down_factor = None
|
|
14964
15064
|
|
|
14965
|
-
if down_factor is None:
|
|
14966
|
-
# --- Processing Options Group ---
|
|
14967
|
-
process_group = QGroupBox("Processing Options")
|
|
14968
|
-
process_layout = QGridLayout()
|
|
14969
|
-
|
|
14970
|
-
# Downsample factor
|
|
14971
|
-
self.down_factor = QLineEdit("0")
|
|
14972
|
-
process_layout.addWidget(QLabel("Downsample Factor (Speeds up calculation at the cost of fidelity):"), 0, 0)
|
|
14973
|
-
process_layout.addWidget(self.down_factor, 0, 1)
|
|
14974
|
-
|
|
14975
|
-
# Cubic checkbox
|
|
14976
|
-
self.cubic = QPushButton("Cubic Downsample")
|
|
14977
|
-
self.cubic.setCheckable(True)
|
|
14978
|
-
self.cubic.setChecked(False)
|
|
14979
|
-
#process_layout.addWidget(QLabel("Use cubic downsample? (Slower but preserves structure better):"), 1, 0)
|
|
14980
|
-
#process_layout.addWidget(self.cubic, 1, 1)
|
|
14981
|
-
|
|
14982
|
-
# Fast dilation checkbox
|
|
14983
|
-
self.fast_dil = QPushButton("Fast-Dil")
|
|
14984
|
-
self.fast_dil.setCheckable(True)
|
|
14985
|
-
self.fast_dil.setChecked(True)
|
|
14986
|
-
#process_layout.addWidget(QLabel("Use Fast Dilation (Higher speed, less accurate with large search regions):"), 2, 0)
|
|
14987
|
-
#process_layout.addWidget(self.fast_dil, 2, 1)
|
|
14988
|
-
|
|
14989
|
-
process_group.setLayout(process_layout)
|
|
14990
|
-
main_layout.addWidget(process_group)
|
|
14991
|
-
else:
|
|
14992
|
-
self.down_factor = down_factor[0]
|
|
14993
|
-
self.cubic = down_factor[1]
|
|
14994
|
-
|
|
14995
|
-
# Fast dilation checkbox (still needed even if down_factor is provided)
|
|
14996
|
-
#process_group = QGroupBox("Processing Options")
|
|
14997
|
-
#process_layout = QGridLayout()
|
|
14998
|
-
|
|
14999
|
-
self.fast_dil = QPushButton("Fast-Dil")
|
|
15000
|
-
self.fast_dil.setCheckable(True)
|
|
15001
|
-
self.fast_dil.setChecked(True)
|
|
15002
|
-
#process_layout.addWidget(QLabel("Use Fast Dilation (Higher speed, less accurate with large search regions):"), 0, 0)
|
|
15003
|
-
#process_layout.addWidget(self.fast_dil, 0, 1)
|
|
15004
|
-
|
|
15005
|
-
#process_group.setLayout(process_layout)
|
|
15006
|
-
#main_layout.addWidget(process_group)
|
|
15007
|
-
|
|
15008
15065
|
# --- Recommended Corrections Group ---
|
|
15009
15066
|
rec_group = QGroupBox("Recommended Corrections")
|
|
15010
15067
|
rec_layout = QGridLayout()
|
|
@@ -15046,6 +15103,39 @@ class GenNodesDialog(QDialog):
|
|
|
15046
15103
|
|
|
15047
15104
|
opt_group.setLayout(opt_layout)
|
|
15048
15105
|
main_layout.addWidget(opt_group)
|
|
15106
|
+
|
|
15107
|
+
# --- Processing Options Group ---
|
|
15108
|
+
process_group = QGroupBox("Processing Options")
|
|
15109
|
+
process_layout = QGridLayout()
|
|
15110
|
+
|
|
15111
|
+
if not called:
|
|
15112
|
+
|
|
15113
|
+
|
|
15114
|
+
# Fast dilation checkbox
|
|
15115
|
+
self.fast_dil = QPushButton("Fast-Dil")
|
|
15116
|
+
self.fast_dil.setCheckable(True)
|
|
15117
|
+
self.fast_dil.setChecked(True)
|
|
15118
|
+
process_layout.addWidget(QLabel("Use Fast Dilation if merging nodes (Parallelized):"), 0, 0)
|
|
15119
|
+
process_layout.addWidget(self.fast_dil, 0, 1)
|
|
15120
|
+
|
|
15121
|
+
# Downsample factor
|
|
15122
|
+
self.down_factor = QLineEdit("0")
|
|
15123
|
+
process_layout.addWidget(QLabel("Downsample Factor (Speeds up calculation at the cost of fidelity):"), 1, 0)
|
|
15124
|
+
process_layout.addWidget(self.down_factor, 1, 1)
|
|
15125
|
+
|
|
15126
|
+
process_group.setLayout(process_layout)
|
|
15127
|
+
main_layout.addWidget(process_group)
|
|
15128
|
+
else:
|
|
15129
|
+
self.down_factor = down_factor
|
|
15130
|
+
|
|
15131
|
+
self.fast_dil = QPushButton("Fast-Dil")
|
|
15132
|
+
self.fast_dil.setCheckable(True)
|
|
15133
|
+
self.fast_dil.setChecked(True)
|
|
15134
|
+
process_layout.addWidget(QLabel("Use Fast Dilation if merging nodes (Parallelized):"), 0, 0)
|
|
15135
|
+
process_layout.addWidget(self.fast_dil, 0, 1)
|
|
15136
|
+
|
|
15137
|
+
process_group.setLayout(process_layout)
|
|
15138
|
+
main_layout.addWidget(process_group)
|
|
15049
15139
|
|
|
15050
15140
|
# Set retain variable but don't add to layout
|
|
15051
15141
|
if not called:
|
|
@@ -15089,31 +15179,26 @@ class GenNodesDialog(QDialog):
|
|
|
15089
15179
|
comp_dil = 0
|
|
15090
15180
|
|
|
15091
15181
|
# Get down_factor
|
|
15092
|
-
if type(self.down_factor) is int:
|
|
15182
|
+
if type(self.down_factor) is int or self.down_factor is None:
|
|
15093
15183
|
down_factor = self.down_factor
|
|
15094
|
-
cubic = self.cubic
|
|
15095
15184
|
else:
|
|
15096
15185
|
try:
|
|
15097
|
-
down_factor = int(self.down_factor.text()) if self.down_factor.text() else
|
|
15186
|
+
down_factor = int(self.down_factor.text()) if self.down_factor.text() else None
|
|
15187
|
+
if down_factor == 0:
|
|
15188
|
+
down_factor = None
|
|
15098
15189
|
except ValueError:
|
|
15099
|
-
down_factor =
|
|
15100
|
-
cubic = self.cubic.isChecked()
|
|
15190
|
+
down_factor = None
|
|
15101
15191
|
|
|
15102
15192
|
try:
|
|
15103
15193
|
retain = self.retain.isChecked()
|
|
15104
15194
|
except:
|
|
15105
15195
|
retain = True
|
|
15106
15196
|
|
|
15107
|
-
if cubic:
|
|
15108
|
-
order = 3
|
|
15109
|
-
else:
|
|
15110
|
-
order = 0
|
|
15111
|
-
|
|
15112
15197
|
auto = self.auto.isChecked()
|
|
15113
15198
|
|
|
15114
15199
|
fastdil = self.fast_dil.isChecked()
|
|
15115
15200
|
|
|
15116
|
-
if down_factor
|
|
15201
|
+
if down_factor is not None:
|
|
15117
15202
|
my_network.edges = n3d.downsample(my_network.edges, down_factor)
|
|
15118
15203
|
|
|
15119
15204
|
if auto:
|
|
@@ -15126,15 +15211,15 @@ class GenNodesDialog(QDialog):
|
|
|
15126
15211
|
max_vol=max_vol,
|
|
15127
15212
|
branch_removal=branch_removal,
|
|
15128
15213
|
comp_dil=comp_dil,
|
|
15129
|
-
order =
|
|
15214
|
+
order = 0,
|
|
15130
15215
|
return_skele = True,
|
|
15131
15216
|
fastdil = fastdil
|
|
15132
15217
|
)
|
|
15133
15218
|
|
|
15134
|
-
if down_factor
|
|
15219
|
+
if down_factor is not None and not self.called:
|
|
15135
15220
|
self.parent().resizing = True
|
|
15136
15221
|
|
|
15137
|
-
my_network.edges = n3d.downsample(my_network.edges, down_factor, order =
|
|
15222
|
+
my_network.edges = n3d.downsample(my_network.edges, down_factor, order = 0)
|
|
15138
15223
|
my_network.xy_scale = my_network.xy_scale * down_factor
|
|
15139
15224
|
my_network.z_scale = my_network.z_scale * down_factor
|
|
15140
15225
|
print("xy_scales and z_scales have been adjusted per downsample. Check image -> properties to manually reset them to 1 if desired.")
|
|
@@ -15241,13 +15326,14 @@ class BranchDialog(QDialog):
|
|
|
15241
15326
|
self.down_factor = QLineEdit("0")
|
|
15242
15327
|
processing_layout.addWidget(QLabel("Internal downsample factor (will recompute nodes):"), 0, 0)
|
|
15243
15328
|
processing_layout.addWidget(self.down_factor, 0, 1)
|
|
15244
|
-
|
|
15245
|
-
#
|
|
15246
|
-
self.
|
|
15247
|
-
self.
|
|
15248
|
-
self.
|
|
15249
|
-
|
|
15250
|
-
|
|
15329
|
+
|
|
15330
|
+
# Add mode selection dropdown
|
|
15331
|
+
self.mode = QComboBox()
|
|
15332
|
+
self.mode.addItems(["Standard", "Fast (May be a little rougher along adjacent labels)"])
|
|
15333
|
+
self.mode.setCurrentIndex(0)
|
|
15334
|
+
processing_layout.addWidget(QLabel("Algorithm (Standard or Fast?):"), 1, 0)
|
|
15335
|
+
processing_layout.addWidget(self.mode, 1, 1)
|
|
15336
|
+
|
|
15251
15337
|
|
|
15252
15338
|
processing_group.setLayout(processing_layout)
|
|
15253
15339
|
main_layout.addWidget(processing_group)
|
|
@@ -15297,17 +15383,20 @@ class BranchDialog(QDialog):
|
|
|
15297
15383
|
try:
|
|
15298
15384
|
|
|
15299
15385
|
try:
|
|
15300
|
-
down_factor = int(self.down_factor.text()) if self.down_factor.text() else
|
|
15386
|
+
down_factor = int(self.down_factor.text()) if self.down_factor.text() else None
|
|
15301
15387
|
except ValueError:
|
|
15302
|
-
down_factor =
|
|
15388
|
+
down_factor = None
|
|
15389
|
+
|
|
15390
|
+
if down_factor == 0:
|
|
15391
|
+
down_factor = None
|
|
15303
15392
|
|
|
15304
15393
|
nodes = self.nodes.isChecked()
|
|
15305
15394
|
GPU = self.GPU.isChecked()
|
|
15306
|
-
cubic = self.cubic.isChecked()
|
|
15307
15395
|
fix = self.fix.isChecked()
|
|
15308
15396
|
fix2 = self.fix2.isChecked()
|
|
15309
15397
|
fix3 = self.fix3.isChecked()
|
|
15310
15398
|
fix4 = self.fix4.isChecked()
|
|
15399
|
+
mode = self.mode.currentIndex()
|
|
15311
15400
|
fix_val = float(self.fix_val.text()) if self.fix_val.text() else None
|
|
15312
15401
|
fix4_val = float(self.fix4_val.text()) if self.fix4_val.text() else 10
|
|
15313
15402
|
seed = int(self.seed.text()) if self.seed.text() else None
|
|
@@ -15320,11 +15409,7 @@ class BranchDialog(QDialog):
|
|
|
15320
15409
|
original_shape = my_network.edges.shape
|
|
15321
15410
|
original_array = copy.deepcopy(my_network.edges)
|
|
15322
15411
|
|
|
15323
|
-
|
|
15324
|
-
self.parent().show_gennodes_dialog(down_factor = [down_factor, cubic], called = True)
|
|
15325
|
-
elif nodes or my_network.nodes is None:
|
|
15326
|
-
self.parent().show_gennodes_dialog(called = True)
|
|
15327
|
-
down_factor = None
|
|
15412
|
+
self.parent().show_gennodes_dialog(down_factor = down_factor, called = True)
|
|
15328
15413
|
|
|
15329
15414
|
if my_network.edges is not None and my_network.nodes is not None and my_network.id_overlay is not None:
|
|
15330
15415
|
|
|
@@ -15333,10 +15418,12 @@ class BranchDialog(QDialog):
|
|
|
15333
15418
|
else:
|
|
15334
15419
|
unify = False
|
|
15335
15420
|
|
|
15336
|
-
output, verts, skeleton, endpoints = n3d.label_branches(my_network.edges, nodes = my_network.nodes, bonus_array = original_array, GPU = GPU, down_factor = down_factor, arrayshape = original_shape, compute = compute, unify = unify, union_val = fix4_val, xy_scale = my_network.xy_scale, z_scale = my_network.z_scale)
|
|
15421
|
+
output, verts, skeleton, endpoints = n3d.label_branches(my_network.edges, nodes = my_network.nodes, bonus_array = original_array, GPU = GPU, down_factor = down_factor, arrayshape = original_shape, compute = compute, unify = unify, union_val = fix4_val, mode = mode, xy_scale = my_network.xy_scale, z_scale = my_network.z_scale)
|
|
15337
15422
|
|
|
15338
15423
|
if fix2:
|
|
15339
15424
|
|
|
15425
|
+
print("Correcting Internal Branches...")
|
|
15426
|
+
|
|
15340
15427
|
temp_network = n3d.Network_3D(nodes = output)
|
|
15341
15428
|
|
|
15342
15429
|
max_val = np.max(temp_network.nodes)
|
|
@@ -15388,6 +15475,7 @@ class BranchDialog(QDialog):
|
|
|
15388
15475
|
self.parent().format_for_upperright_table(tortuosity_dict, 'BranchID', 'Tortuosity', 'Branch Tortuosities')
|
|
15389
15476
|
#self.parent().format_for_upperright_table(angle_dict, 'Vertex ID', title, 'Branch Angles')
|
|
15390
15477
|
|
|
15478
|
+
scalings = my_network.xy_scale, my_network.z_scale
|
|
15391
15479
|
|
|
15392
15480
|
if down_factor is not None:
|
|
15393
15481
|
|
|
@@ -15397,6 +15485,9 @@ class BranchDialog(QDialog):
|
|
|
15397
15485
|
self.parent().reset(id_overlay = True)
|
|
15398
15486
|
self.parent().update_display(dims = (output.shape[1], output.shape[2]))
|
|
15399
15487
|
|
|
15488
|
+
my_network.xy_scale, my_network.z_scale = scalings
|
|
15489
|
+
|
|
15490
|
+
|
|
15400
15491
|
self.parent().load_channel(1, channel_data = output, data = True)
|
|
15401
15492
|
|
|
15402
15493
|
self.parent().update_display(preserve_zoom = (self.parent().ax.get_xlim(), self.parent().ax.get_ylim()))
|
|
@@ -15906,7 +15997,7 @@ class CalcAllDialog(QDialog):
|
|
|
15906
15997
|
prev_gpu = False
|
|
15907
15998
|
prev_label_nodes = True
|
|
15908
15999
|
prev_inners = True
|
|
15909
|
-
prev_fastdil =
|
|
16000
|
+
prev_fastdil = True
|
|
15910
16001
|
prev_overlays = False
|
|
15911
16002
|
prev_updates = True
|
|
15912
16003
|
|
|
@@ -15979,10 +16070,10 @@ class CalcAllDialog(QDialog):
|
|
|
15979
16070
|
self.gpu.setChecked(self.prev_gpu)
|
|
15980
16071
|
#speedup_layout.addRow("Use GPU:", self.gpu)
|
|
15981
16072
|
|
|
15982
|
-
self.fastdil = QPushButton("Fast
|
|
16073
|
+
self.fastdil = QPushButton("Fast Search")
|
|
15983
16074
|
self.fastdil.setCheckable(True)
|
|
15984
16075
|
self.fastdil.setChecked(self.prev_fastdil)
|
|
15985
|
-
|
|
16076
|
+
speedup_layout.addRow("Use Fast Search (Parallelized searching, search regions may be a tad rougher along adjacent boundaries):", self.fastdil)
|
|
15986
16077
|
|
|
15987
16078
|
main_layout.addWidget(speedup_group)
|
|
15988
16079
|
|