nettracer3d 0.5.9__py3-none-any.whl → 0.6.1__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/nettracer.py +0 -23
- nettracer3d/nettracer_gui.py +432 -219
- nettracer3d/segmenter.py +670 -54
- {nettracer3d-0.5.9.dist-info → nettracer3d-0.6.1.dist-info}/METADATA +7 -3
- {nettracer3d-0.5.9.dist-info → nettracer3d-0.6.1.dist-info}/RECORD +9 -10
- nettracer3d/hub_getter.py +0 -248
- {nettracer3d-0.5.9.dist-info → nettracer3d-0.6.1.dist-info}/LICENSE +0 -0
- {nettracer3d-0.5.9.dist-info → nettracer3d-0.6.1.dist-info}/WHEEL +0 -0
- {nettracer3d-0.5.9.dist-info → nettracer3d-0.6.1.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.5.9.dist-info → nettracer3d-0.6.1.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -840,7 +840,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
840
840
|
select_nodes.triggered.connect(lambda: self.handle_select_all(edges = False, nodes = True))
|
|
841
841
|
select_both.triggered.connect(lambda: self.handle_select_all(edges = True))
|
|
842
842
|
select_edges.triggered.connect(lambda: self.handle_select_all(edges = True, nodes = False))
|
|
843
|
-
if self.highlight_overlay is not None:
|
|
843
|
+
if self.highlight_overlay is not None or self.mini_overlay_data is not None:
|
|
844
844
|
highlight_select = context_menu.addAction("Add highlight in network selection")
|
|
845
845
|
highlight_select.triggered.connect(self.handle_highlight_select)
|
|
846
846
|
show_remove_menu.triggered.connect(self.handle_remove_points)
|
|
@@ -1259,19 +1259,28 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1259
1259
|
nodes = list(np.unique(my_network.nodes))
|
|
1260
1260
|
if nodes[0] == 0:
|
|
1261
1261
|
del nodes[0]
|
|
1262
|
+
num = (self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2])
|
|
1263
|
+
print(f"Found {len(nodes)} node objects")
|
|
1262
1264
|
else:
|
|
1263
1265
|
nodes = []
|
|
1264
1266
|
if edges:
|
|
1265
1267
|
edges = list(np.unique(my_network.edges))
|
|
1268
|
+
num = (self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2])
|
|
1266
1269
|
if edges[0] == 0:
|
|
1267
1270
|
del edges[0]
|
|
1271
|
+
print(f"Found {len(edges)} edge objects")
|
|
1268
1272
|
else:
|
|
1269
1273
|
edges = []
|
|
1270
1274
|
|
|
1271
|
-
self.clicked_values['nodes']
|
|
1272
|
-
self.clicked_values['edges']
|
|
1275
|
+
self.clicked_values['nodes'] = nodes
|
|
1276
|
+
self.clicked_values['edges'] = edges
|
|
1273
1277
|
|
|
1274
|
-
|
|
1278
|
+
|
|
1279
|
+
if num > self.mini_thresh:
|
|
1280
|
+
self.mini_overlay = True
|
|
1281
|
+
self.create_mini_overlay(node_indices = nodes, edge_indices = edges)
|
|
1282
|
+
else:
|
|
1283
|
+
self.create_highlight_overlay(edge_indices = self.clicked_values['edges'], node_indices = self.clicked_values['nodes'])
|
|
1275
1284
|
|
|
1276
1285
|
except Exception as e:
|
|
1277
1286
|
print(f"Error: {e}")
|
|
@@ -2466,6 +2475,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2466
2475
|
searchoverlay_action.triggered.connect(self.show_search_dialog)
|
|
2467
2476
|
shuffle_action = overlay_menu.addAction("Shuffle")
|
|
2468
2477
|
shuffle_action.triggered.connect(self.show_shuffle_dialog)
|
|
2478
|
+
arbitrary_action = image_menu.addAction("Select Objects")
|
|
2479
|
+
arbitrary_action.triggered.connect(self.show_arbitrary_dialog)
|
|
2469
2480
|
show3d_action = image_menu.addAction("Show 3D (Napari)")
|
|
2470
2481
|
show3d_action.triggered.connect(self.show3d_dialog)
|
|
2471
2482
|
|
|
@@ -2596,6 +2607,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2596
2607
|
dialog = WatershedDialog(self)
|
|
2597
2608
|
dialog.exec()
|
|
2598
2609
|
|
|
2610
|
+
def show_arbitrary_dialog(self):
|
|
2611
|
+
"""Show the arbitrary selection dialog."""
|
|
2612
|
+
dialog = ArbitraryDialog(self)
|
|
2613
|
+
dialog.exec()
|
|
2614
|
+
|
|
2599
2615
|
def show_invert_dialog(self):
|
|
2600
2616
|
"""Show the watershed parameter dialog."""
|
|
2601
2617
|
dialog = InvertDialog(self)
|
|
@@ -3504,8 +3520,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3504
3520
|
current_ylim = self.ax.get_ylim() if hasattr(self, 'ax') and self.ax.get_ylim() != (0, 1) else None
|
|
3505
3521
|
# Convert slider values (0-100) to data values (0-1)
|
|
3506
3522
|
min_val, max_val = values
|
|
3507
|
-
self.channel_brightness[channel_index]['min'] = min_val /
|
|
3508
|
-
self.channel_brightness[channel_index]['max'] = max_val /
|
|
3523
|
+
self.channel_brightness[channel_index]['min'] = min_val / 65535 #Accomodate 32 bit data?
|
|
3524
|
+
self.channel_brightness[channel_index]['max'] = max_val / 65535
|
|
3509
3525
|
self.update_display(preserve_zoom = (current_xlim, current_ylim))
|
|
3510
3526
|
|
|
3511
3527
|
|
|
@@ -3514,206 +3530,211 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3514
3530
|
def update_display(self, preserve_zoom=None, dims = None, called = False, reset_resize = False, begin_paint = False):
|
|
3515
3531
|
"""Update the display with currently visible channels and highlight overlay."""
|
|
3516
3532
|
|
|
3517
|
-
|
|
3518
|
-
# Store/update the static background with current zoom level
|
|
3519
|
-
self.static_background = self.canvas.copy_from_bbox(self.ax.bbox)
|
|
3533
|
+
try:
|
|
3520
3534
|
|
|
3535
|
+
if begin_paint:
|
|
3536
|
+
# Store/update the static background with current zoom level
|
|
3537
|
+
self.static_background = self.canvas.copy_from_bbox(self.ax.bbox)
|
|
3521
3538
|
|
|
3522
|
-
self.figure.clear()
|
|
3523
3539
|
|
|
3524
|
-
|
|
3525
|
-
active_channels = [i for i in range(4) if self.channel_data[i] is not None]
|
|
3526
|
-
if dims is None:
|
|
3527
|
-
if active_channels:
|
|
3528
|
-
dims = [(self.channel_data[i].shape[1:3] if len(self.channel_data[i].shape) >= 3 else
|
|
3529
|
-
self.channel_data[i].shape) for i in active_channels]
|
|
3530
|
-
min_height = min(d[0] for d in dims)
|
|
3531
|
-
min_width = min(d[1] for d in dims)
|
|
3532
|
-
else:
|
|
3533
|
-
min_height = 1
|
|
3534
|
-
min_width = 1
|
|
3535
|
-
else:
|
|
3536
|
-
min_height = dims[0]
|
|
3537
|
-
min_width = dims[1]
|
|
3540
|
+
self.figure.clear()
|
|
3538
3541
|
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
# Store current zoom limits if they exist and weren't provided
|
|
3548
|
-
|
|
3549
|
-
current_xlim, current_ylim = preserve_zoom if preserve_zoom else (None, None)
|
|
3550
|
-
|
|
3551
|
-
# Define base colors for each channel with increased intensity
|
|
3552
|
-
base_colors = self.base_colors
|
|
3553
|
-
# Set only the axes (image area) background to black
|
|
3554
|
-
self.ax.set_facecolor('black')
|
|
3555
|
-
|
|
3556
|
-
# Display each visible channel
|
|
3557
|
-
for channel in range(4):
|
|
3558
|
-
if (self.channel_visible[channel] and
|
|
3559
|
-
self.channel_data[channel] is not None):
|
|
3560
|
-
|
|
3561
|
-
# Check if we're dealing with RGB data
|
|
3562
|
-
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)
|
|
3563
|
-
|
|
3564
|
-
if len(self.channel_data[channel].shape) == 3 and not is_rgb:
|
|
3565
|
-
current_image = self.channel_data[channel][self.current_slice, :, :]
|
|
3566
|
-
elif is_rgb:
|
|
3567
|
-
current_image = self.channel_data[channel][self.current_slice] # Already has RGB channels
|
|
3542
|
+
# Get active channels and their dimensions
|
|
3543
|
+
active_channels = [i for i in range(4) if self.channel_data[i] is not None]
|
|
3544
|
+
if dims is None:
|
|
3545
|
+
if active_channels:
|
|
3546
|
+
dims = [(self.channel_data[i].shape[1:3] if len(self.channel_data[i].shape) >= 3 else
|
|
3547
|
+
self.channel_data[i].shape) for i in active_channels]
|
|
3548
|
+
min_height = min(d[0] for d in dims)
|
|
3549
|
+
min_width = min(d[1] for d in dims)
|
|
3568
3550
|
else:
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
alpha=0.7)
|
|
3575
|
-
elif is_rgb and self.channel_data[channel].shape[-1] == 4:
|
|
3576
|
-
self.ax.imshow(current_image) #For images that already have an alpha value and RGB, don't update alpha
|
|
3551
|
+
min_height = 1
|
|
3552
|
+
min_width = 1
|
|
3553
|
+
else:
|
|
3554
|
+
min_height = dims[0]
|
|
3555
|
+
min_width = dims[1]
|
|
3577
3556
|
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3557
|
+
# Set axes limits before displaying any images
|
|
3558
|
+
self.ax.set_xlim(-0.5, min_width - 0.5)
|
|
3559
|
+
self.ax.set_ylim(min_height - 0.5, -0.5)
|
|
3560
|
+
|
|
3561
|
+
# Create subplot with tight layout and white figure background
|
|
3562
|
+
self.figure.patch.set_facecolor('white')
|
|
3563
|
+
self.ax = self.figure.add_subplot(111)
|
|
3564
|
+
|
|
3565
|
+
# Store current zoom limits if they exist and weren't provided
|
|
3566
|
+
|
|
3567
|
+
current_xlim, current_ylim = preserve_zoom if preserve_zoom else (None, None)
|
|
3568
|
+
|
|
3569
|
+
# Define base colors for each channel with increased intensity
|
|
3570
|
+
base_colors = self.base_colors
|
|
3571
|
+
# Set only the axes (image area) background to black
|
|
3572
|
+
self.ax.set_facecolor('black')
|
|
3573
|
+
|
|
3574
|
+
# Display each visible channel
|
|
3575
|
+
for channel in range(4):
|
|
3576
|
+
if (self.channel_visible[channel] and
|
|
3577
|
+
self.channel_data[channel] is not None):
|
|
3583
3578
|
|
|
3584
|
-
#
|
|
3585
|
-
|
|
3586
|
-
vmin = img_min
|
|
3587
|
-
vmax = img_min + 1
|
|
3588
|
-
else:
|
|
3589
|
-
vmin = img_min + (img_max - img_min) * self.channel_brightness[channel]['min']
|
|
3590
|
-
vmax = img_min + (img_max - img_min) * self.channel_brightness[channel]['max']
|
|
3579
|
+
# Check if we're dealing with RGB data
|
|
3580
|
+
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)
|
|
3591
3581
|
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3582
|
+
if len(self.channel_data[channel].shape) == 3 and not is_rgb:
|
|
3583
|
+
current_image = self.channel_data[channel][self.current_slice, :, :]
|
|
3584
|
+
elif is_rgb:
|
|
3585
|
+
current_image = self.channel_data[channel][self.current_slice] # Already has RGB channels
|
|
3595
3586
|
else:
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
if
|
|
3599
|
-
|
|
3600
|
-
f'custom_{channel}',
|
|
3601
|
-
[(0, 0, 0, 0), # transparent for 0
|
|
3602
|
-
(0.5, 1, 0.5, 1), # light green for 1
|
|
3603
|
-
(1, 0.5, 0.5, 1)] # light red for 2
|
|
3604
|
-
)
|
|
3587
|
+
current_image = self.channel_data[channel]
|
|
3588
|
+
|
|
3589
|
+
if is_rgb and self.channel_data[channel].shape[-1] == 3:
|
|
3590
|
+
# For RGB images, just display directly without colormap
|
|
3605
3591
|
self.ax.imshow(current_image,
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
interpolation='nearest',
|
|
3611
|
-
extent=(-0.5, min_width-0.5, min_height-0.5, -0.5))
|
|
3592
|
+
alpha=0.7)
|
|
3593
|
+
elif is_rgb and self.channel_data[channel].shape[-1] == 4:
|
|
3594
|
+
self.ax.imshow(current_image) #For images that already have an alpha value and RGB, don't update alpha
|
|
3595
|
+
|
|
3612
3596
|
else:
|
|
3613
|
-
#
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
[(0,0,0,0), (*color,1)]
|
|
3618
|
-
)
|
|
3597
|
+
# Regular channel processing with colormap
|
|
3598
|
+
# Calculate brightness/contrast limits from entire volume
|
|
3599
|
+
img_min = self.min_max[channel][0]
|
|
3600
|
+
img_max = self.min_max[channel][1]
|
|
3619
3601
|
|
|
3620
|
-
#
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
# Draw line if both points are on current slice
|
|
3699
|
-
if z1 == z2 == self.current_slice:
|
|
3700
|
-
self.ax.plot([x1, x2], [y1, y2], 'r--', alpha=0.5)
|
|
3701
|
-
|
|
3702
|
-
if active_channels:
|
|
3703
|
-
self.ax.set_xlim(-0.5, min_width - 0.5)
|
|
3704
|
-
self.ax.set_ylim(min_height - 0.5, -0.5)
|
|
3602
|
+
# Calculate vmin and vmax, ensuring we don't get a zero range
|
|
3603
|
+
if img_min == img_max:
|
|
3604
|
+
vmin = img_min
|
|
3605
|
+
vmax = img_min + 1
|
|
3606
|
+
else:
|
|
3607
|
+
vmin = img_min + (img_max - img_min) * self.channel_brightness[channel]['min']
|
|
3608
|
+
vmax = img_min + (img_max - img_min) * self.channel_brightness[channel]['max']
|
|
3609
|
+
|
|
3610
|
+
# Normalize the image safely
|
|
3611
|
+
if vmin == vmax:
|
|
3612
|
+
normalized_image = np.zeros_like(current_image)
|
|
3613
|
+
else:
|
|
3614
|
+
normalized_image = np.clip((current_image - vmin) / (vmax - vmin), 0, 1)
|
|
3615
|
+
|
|
3616
|
+
if channel == 2 and self.machine_window is not None:
|
|
3617
|
+
custom_cmap = LinearSegmentedColormap.from_list(
|
|
3618
|
+
f'custom_{channel}',
|
|
3619
|
+
[(0, 0, 0, 0), # transparent for 0
|
|
3620
|
+
(0.5, 1, 0.5, 1), # light green for 1
|
|
3621
|
+
(1, 0.5, 0.5, 1)] # light red for 2
|
|
3622
|
+
)
|
|
3623
|
+
self.ax.imshow(current_image,
|
|
3624
|
+
cmap=custom_cmap,
|
|
3625
|
+
vmin=0,
|
|
3626
|
+
vmax=2,
|
|
3627
|
+
alpha=0.7,
|
|
3628
|
+
interpolation='nearest',
|
|
3629
|
+
extent=(-0.5, min_width-0.5, min_height-0.5, -0.5))
|
|
3630
|
+
else:
|
|
3631
|
+
# Create custom colormap with higher intensity
|
|
3632
|
+
color = base_colors[channel]
|
|
3633
|
+
custom_cmap = LinearSegmentedColormap.from_list(
|
|
3634
|
+
f'custom_{channel}',
|
|
3635
|
+
[(0,0,0,0), (*color,1)]
|
|
3636
|
+
)
|
|
3637
|
+
|
|
3638
|
+
# Display the image with slightly higher alpha
|
|
3639
|
+
self.ax.imshow(normalized_image,
|
|
3640
|
+
alpha=0.7,
|
|
3641
|
+
cmap=custom_cmap,
|
|
3642
|
+
vmin=0,
|
|
3643
|
+
vmax=1,
|
|
3644
|
+
extent=(-0.5, min_width-0.5, min_height-0.5, -0.5))
|
|
3645
|
+
|
|
3646
|
+
if self.preview and not called:
|
|
3647
|
+
self.create_highlight_overlay_slice(self.targs, bounds = self.bounds)
|
|
3648
|
+
|
|
3649
|
+
# Add highlight overlay if it exists
|
|
3650
|
+
if self.mini_overlay and self.highlight and self.machine_window is None:
|
|
3651
|
+
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
3652
|
+
'highlight',
|
|
3653
|
+
[(0, 0, 0, 0), (1, 1, 0, 1)] # yellow
|
|
3654
|
+
)
|
|
3655
|
+
self.ax.imshow(self.mini_overlay_data,
|
|
3656
|
+
cmap=highlight_cmap,
|
|
3657
|
+
alpha=0.5)
|
|
3658
|
+
elif self.highlight_overlay is not None and self.highlight and self.machine_window is None:
|
|
3659
|
+
highlight_slice = self.highlight_overlay[self.current_slice]
|
|
3660
|
+
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
3661
|
+
'highlight',
|
|
3662
|
+
[(0, 0, 0, 0), (1, 1, 0, 1)] # yellow
|
|
3663
|
+
)
|
|
3664
|
+
self.ax.imshow(highlight_slice,
|
|
3665
|
+
cmap=highlight_cmap,
|
|
3666
|
+
alpha=0.5)
|
|
3667
|
+
elif self.highlight_overlay is not None and self.highlight:
|
|
3668
|
+
highlight_slice = self.highlight_overlay[self.current_slice]
|
|
3669
|
+
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
3670
|
+
'highlight',
|
|
3671
|
+
[(0, 0, 0, 0), # transparent for 0
|
|
3672
|
+
(1, 1, 0, 1), # bright yellow for 1
|
|
3673
|
+
(0, 0.7, 1, 1)] # cool blue for 2
|
|
3674
|
+
)
|
|
3675
|
+
self.ax.imshow(highlight_slice,
|
|
3676
|
+
cmap=highlight_cmap,
|
|
3677
|
+
vmin=0,
|
|
3678
|
+
vmax=2, # Important: set vmax to 2 to accommodate both values
|
|
3679
|
+
alpha=0.5)
|
|
3705
3680
|
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
self.ax.
|
|
3712
|
-
self.ax.
|
|
3713
|
-
|
|
3714
|
-
|
|
3681
|
+
|
|
3682
|
+
|
|
3683
|
+
|
|
3684
|
+
# Style the axes
|
|
3685
|
+
self.ax.set_xlabel('X')
|
|
3686
|
+
self.ax.set_ylabel('Y')
|
|
3687
|
+
self.ax.set_title(f'Slice {self.current_slice}')
|
|
3688
|
+
|
|
3689
|
+
# Make axis labels and ticks black for visibility against white background
|
|
3690
|
+
self.ax.xaxis.label.set_color('black')
|
|
3691
|
+
self.ax.yaxis.label.set_color('black')
|
|
3692
|
+
self.ax.title.set_color('black')
|
|
3693
|
+
self.ax.tick_params(colors='black')
|
|
3694
|
+
for spine in self.ax.spines.values():
|
|
3695
|
+
spine.set_color('black')
|
|
3696
|
+
|
|
3697
|
+
# Adjust the layout to ensure the plot fits well in the figure
|
|
3698
|
+
self.figure.tight_layout()
|
|
3699
|
+
|
|
3700
|
+
# Redraw measurement points and their labels
|
|
3701
|
+
for point in self.measurement_points:
|
|
3702
|
+
x1, y1, z1 = point['point1']
|
|
3703
|
+
x2, y2, z2 = point['point2']
|
|
3704
|
+
pair_idx = point['pair_index']
|
|
3705
|
+
|
|
3706
|
+
# Draw points and labels if they're on current slice
|
|
3707
|
+
if z1 == self.current_slice:
|
|
3708
|
+
self.ax.plot(x1, y1, 'yo', markersize=8)
|
|
3709
|
+
self.ax.text(x1, y1+5, str(pair_idx),
|
|
3710
|
+
color='white', ha='center', va='bottom')
|
|
3711
|
+
if z2 == self.current_slice:
|
|
3712
|
+
self.ax.plot(x2, y2, 'yo', markersize=8)
|
|
3713
|
+
self.ax.text(x2, y2+5, str(pair_idx),
|
|
3714
|
+
color='white', ha='center', va='bottom')
|
|
3715
|
+
|
|
3716
|
+
# Draw line if both points are on current slice
|
|
3717
|
+
if z1 == z2 == self.current_slice:
|
|
3718
|
+
self.ax.plot([x1, x2], [y1, y2], 'r--', alpha=0.5)
|
|
3715
3719
|
|
|
3716
|
-
|
|
3720
|
+
if active_channels:
|
|
3721
|
+
self.ax.set_xlim(-0.5, min_width - 0.5)
|
|
3722
|
+
self.ax.set_ylim(min_height - 0.5, -0.5)
|
|
3723
|
+
|
|
3724
|
+
if self.resizing:
|
|
3725
|
+
self.original_xlim = self.ax.get_xlim()
|
|
3726
|
+
self.original_ylim = self.ax.get_ylim()
|
|
3727
|
+
# Restore zoom limits if they existed
|
|
3728
|
+
if current_xlim is not None and current_ylim is not None:
|
|
3729
|
+
self.ax.set_xlim(current_xlim)
|
|
3730
|
+
self.ax.set_ylim(current_ylim)
|
|
3731
|
+
if reset_resize:
|
|
3732
|
+
self.resizing = False
|
|
3733
|
+
|
|
3734
|
+
self.canvas.draw()
|
|
3735
|
+
|
|
3736
|
+
except:
|
|
3737
|
+
pass
|
|
3717
3738
|
|
|
3718
3739
|
def update_display_slice(self, channel, preserve_zoom=None):
|
|
3719
3740
|
"""Ultra minimal update that only changes the paint channel's data"""
|
|
@@ -4631,14 +4652,14 @@ class BrightnessContrastDialog(QDialog):
|
|
|
4631
4652
|
# Create range slider
|
|
4632
4653
|
slider = QRangeSlider(Qt.Orientation.Horizontal)
|
|
4633
4654
|
slider.setMinimum(0)
|
|
4634
|
-
slider.setMaximum(
|
|
4635
|
-
slider.setValue((0,
|
|
4655
|
+
slider.setMaximum(65535)
|
|
4656
|
+
slider.setValue((0, 65535))
|
|
4636
4657
|
self.brightness_sliders.append(slider)
|
|
4637
4658
|
|
|
4638
4659
|
# Create max value input
|
|
4639
4660
|
max_input = QLineEdit()
|
|
4640
4661
|
max_input.setFixedWidth(50)
|
|
4641
|
-
max_input.setText("
|
|
4662
|
+
max_input.setText("65535")
|
|
4642
4663
|
self.max_inputs.append(max_input)
|
|
4643
4664
|
|
|
4644
4665
|
# Add all components to slider container
|
|
@@ -4687,8 +4708,8 @@ class BrightnessContrastDialog(QDialog):
|
|
|
4687
4708
|
max_val = self.parse_input_value(self.max_inputs[channel].text())
|
|
4688
4709
|
current_min, current_max = self.brightness_sliders[channel].value()
|
|
4689
4710
|
|
|
4690
|
-
if max_val >
|
|
4691
|
-
max_val =
|
|
4711
|
+
if max_val > 65535:
|
|
4712
|
+
max_val = 65535
|
|
4692
4713
|
# Ensure max doesn't go below min
|
|
4693
4714
|
max_val = max(max_val, current_min + 1)
|
|
4694
4715
|
|
|
@@ -4708,8 +4729,8 @@ class BrightnessContrastDialog(QDialog):
|
|
|
4708
4729
|
value = float(text)
|
|
4709
4730
|
# Round to nearest integer
|
|
4710
4731
|
value = int(round(value))
|
|
4711
|
-
# Clamp between 0 and
|
|
4712
|
-
return max(0, min(
|
|
4732
|
+
# Clamp between 0 and 65535
|
|
4733
|
+
return max(0, min(65535, value))
|
|
4713
4734
|
except ValueError:
|
|
4714
4735
|
raise ValueError("Invalid input")
|
|
4715
4736
|
|
|
@@ -4756,7 +4777,168 @@ class ColorDialog(QDialog):
|
|
|
4756
4777
|
|
|
4757
4778
|
# Update the display
|
|
4758
4779
|
self.parent().update_display()
|
|
4759
|
-
self.accept()
|
|
4780
|
+
self.accept()
|
|
4781
|
+
|
|
4782
|
+
class ArbitraryDialog(QDialog):
|
|
4783
|
+
def __init__(self, parent=None):
|
|
4784
|
+
super().__init__(parent)
|
|
4785
|
+
self.setWindowTitle("Arbitrary Selector")
|
|
4786
|
+
self.setModal(True)
|
|
4787
|
+
|
|
4788
|
+
# Main layout
|
|
4789
|
+
main_layout = QVBoxLayout(self)
|
|
4790
|
+
|
|
4791
|
+
# Form layout for inputs
|
|
4792
|
+
layout = QFormLayout()
|
|
4793
|
+
main_layout.addLayout(layout)
|
|
4794
|
+
|
|
4795
|
+
self.mode_selector = QComboBox()
|
|
4796
|
+
self.mode_selector.addItems(["nodes", "edges"])
|
|
4797
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
4798
|
+
layout.addRow("Type to select:", self.mode_selector)
|
|
4799
|
+
|
|
4800
|
+
# Selection section
|
|
4801
|
+
excel_button = QPushButton("Import selection from spreadsheet (Col 1)")
|
|
4802
|
+
excel_button.clicked.connect(self.import_excel)
|
|
4803
|
+
layout.addWidget(excel_button)
|
|
4804
|
+
|
|
4805
|
+
self.select = QLineEdit("")
|
|
4806
|
+
layout.addRow("Select the following? (Use this format - '1,2,3,4' etc:", self.select)
|
|
4807
|
+
|
|
4808
|
+
# Deselection section
|
|
4809
|
+
deexcel_button = QPushButton("Import deselection from spreadsheet (Col 1)")
|
|
4810
|
+
deexcel_button.clicked.connect(self.import_deexcel)
|
|
4811
|
+
layout.addWidget(deexcel_button)
|
|
4812
|
+
|
|
4813
|
+
self.deselect = QLineEdit("")
|
|
4814
|
+
layout.addRow("Deselect the following? (Use this format - '1,2,3,4' etc:", self.deselect)
|
|
4815
|
+
|
|
4816
|
+
# Run button
|
|
4817
|
+
run_button = QPushButton("Run")
|
|
4818
|
+
run_button.clicked.connect(self.process_selections)
|
|
4819
|
+
main_layout.addWidget(run_button)
|
|
4820
|
+
|
|
4821
|
+
def import_excel(self):
|
|
4822
|
+
"""Import selection from Excel/CSV and populate the select QLineEdit."""
|
|
4823
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
4824
|
+
self, "Select File", "", "Spreadsheet Files (*.xlsx *.xls *.csv)"
|
|
4825
|
+
)
|
|
4826
|
+
|
|
4827
|
+
if file_path:
|
|
4828
|
+
try:
|
|
4829
|
+
selection_list = self.read_selection_from_file(file_path)
|
|
4830
|
+
selection_string = ",".join(map(str, selection_list))
|
|
4831
|
+
self.select.setText(selection_string)
|
|
4832
|
+
except Exception as e:
|
|
4833
|
+
QMessageBox.critical(self, "Error", f"Failed to import: {str(e)}")
|
|
4834
|
+
|
|
4835
|
+
def import_deexcel(self):
|
|
4836
|
+
"""Import deselection from Excel/CSV and populate the deselect QLineEdit."""
|
|
4837
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
4838
|
+
self, "Select File", "", "Spreadsheet Files (*.xlsx *.xls *.csv)"
|
|
4839
|
+
)
|
|
4840
|
+
|
|
4841
|
+
if file_path:
|
|
4842
|
+
try:
|
|
4843
|
+
deselection_list = self.read_selection_from_file(file_path)
|
|
4844
|
+
deselection_string = ",".join(map(str, deselection_list))
|
|
4845
|
+
self.deselect.setText(deselection_string)
|
|
4846
|
+
except Exception as e:
|
|
4847
|
+
QMessageBox.critical(self, "Error", f"Failed to import: {str(e)}")
|
|
4848
|
+
|
|
4849
|
+
def read_selection_from_file(self, file_path):
|
|
4850
|
+
"""Read selection IDs from Excel/CSV file and return as a list."""
|
|
4851
|
+
# Determine file type and read accordingly
|
|
4852
|
+
if file_path.lower().endswith('.csv'):
|
|
4853
|
+
# Read CSV file
|
|
4854
|
+
df = pd.read_csv(file_path, header=None)
|
|
4855
|
+
else:
|
|
4856
|
+
# Read Excel file
|
|
4857
|
+
df = pd.read_excel(file_path, header=None)
|
|
4858
|
+
|
|
4859
|
+
# Check if first row looks like a header
|
|
4860
|
+
first_row = df.iloc[0]
|
|
4861
|
+
if all(isinstance(x, str) for x in first_row):
|
|
4862
|
+
# First row is likely a header, skip it
|
|
4863
|
+
values = df.iloc[1:, 0].dropna().tolist()
|
|
4864
|
+
else:
|
|
4865
|
+
# No header, use all rows
|
|
4866
|
+
values = df.iloc[:, 0].dropna().tolist()
|
|
4867
|
+
|
|
4868
|
+
# Convert to integers when possible, keep floats when necessary
|
|
4869
|
+
processed_values = []
|
|
4870
|
+
for val in values:
|
|
4871
|
+
try:
|
|
4872
|
+
# Try to convert to int first
|
|
4873
|
+
processed_values.append(int(val))
|
|
4874
|
+
except ValueError:
|
|
4875
|
+
try:
|
|
4876
|
+
# If int fails, try float
|
|
4877
|
+
processed_values.append(float(val))
|
|
4878
|
+
except ValueError:
|
|
4879
|
+
# Skip values that can't be converted to numbers
|
|
4880
|
+
continue
|
|
4881
|
+
|
|
4882
|
+
return processed_values
|
|
4883
|
+
|
|
4884
|
+
def process_selections(self):
|
|
4885
|
+
"""Process the selection and deselection inputs."""
|
|
4886
|
+
try:
|
|
4887
|
+
from ast import literal_eval
|
|
4888
|
+
# Get values from QLineEdit fields
|
|
4889
|
+
select_text = self.select.text()
|
|
4890
|
+
deselect_text = self.deselect.text()
|
|
4891
|
+
|
|
4892
|
+
# Format text for literal_eval by adding brackets
|
|
4893
|
+
if select_text:
|
|
4894
|
+
select_list = literal_eval(f"[{select_text}]")
|
|
4895
|
+
else:
|
|
4896
|
+
select_list = []
|
|
4897
|
+
|
|
4898
|
+
if deselect_text:
|
|
4899
|
+
deselect_list = literal_eval(f"[{deselect_text}]")
|
|
4900
|
+
else:
|
|
4901
|
+
deselect_list = []
|
|
4902
|
+
|
|
4903
|
+
# Get the current mode
|
|
4904
|
+
mode = self.mode_selector.currentText()
|
|
4905
|
+
|
|
4906
|
+
if mode == 'nodes':
|
|
4907
|
+
num = self.parent().channel_data[0].shape[0] * self.parent().channel_data[0].shape[1] * self.parent().channel_data[0].shape[2]
|
|
4908
|
+
else:
|
|
4909
|
+
num = self.parent().channel_data[1].shape[0] * self.parent().channel_data[1].shape[1] * self.parent().channel_data[1].shape[2]
|
|
4910
|
+
|
|
4911
|
+
|
|
4912
|
+
for item in deselect_list:
|
|
4913
|
+
try:
|
|
4914
|
+
self.parent().clicked_values[mode].remove(item)
|
|
4915
|
+
except:
|
|
4916
|
+
pass #Forgive mistakes
|
|
4917
|
+
|
|
4918
|
+
for item in select_list:
|
|
4919
|
+
try:
|
|
4920
|
+
self.parent().clicked_values[mode].append(item)
|
|
4921
|
+
except:
|
|
4922
|
+
pass
|
|
4923
|
+
|
|
4924
|
+
self.parent().clicked_values[mode] = list(set(self.parent().clicked_values[mode]))
|
|
4925
|
+
|
|
4926
|
+
if num > self.parent().mini_thresh:
|
|
4927
|
+
self.parent().mini_overlay = True
|
|
4928
|
+
self.parent().create_mini_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
|
|
4929
|
+
else:
|
|
4930
|
+
self.parent().create_highlight_overlay(
|
|
4931
|
+
node_indices=self.parent().clicked_values['nodes'],
|
|
4932
|
+
edge_indices=self.parent().clicked_values['edges']
|
|
4933
|
+
)
|
|
4934
|
+
|
|
4935
|
+
|
|
4936
|
+
|
|
4937
|
+
# Close the dialog after processing
|
|
4938
|
+
self.accept()
|
|
4939
|
+
|
|
4940
|
+
except Exception as e:
|
|
4941
|
+
QMessageBox.critical(self, "Error", f"Error processing selections: {str(e)}")
|
|
4760
4942
|
|
|
4761
4943
|
class Show3dDialog(QDialog):
|
|
4762
4944
|
def __init__(self, parent=None):
|
|
@@ -4818,7 +5000,7 @@ class Show3dDialog(QDialog):
|
|
|
4818
5000
|
if visible:
|
|
4819
5001
|
arrays_4d.append(channel)
|
|
4820
5002
|
|
|
4821
|
-
if self.parent().highlight_overlay is not None:
|
|
5003
|
+
if self.parent().highlight_overlay is not None or self.parent().mini_overlay_data is not None:
|
|
4822
5004
|
if self.parent().mini_overlay == True:
|
|
4823
5005
|
self.parent().create_highlight_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
|
|
4824
5006
|
arrays_3d.append(self.parent().highlight_overlay)
|
|
@@ -5048,19 +5230,24 @@ class ShuffleDialog(QDialog):
|
|
|
5048
5230
|
target_data = self.parent().channel_data[accepted_target]
|
|
5049
5231
|
|
|
5050
5232
|
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
self.parent().highlight_overlay = n3d.binarize(target_data)
|
|
5054
|
-
else:
|
|
5055
|
-
self.parent().load_channel(accepted_mode, channel_data = target_data, data = True)
|
|
5233
|
+
try:
|
|
5234
|
+
if accepted_mode == 4:
|
|
5056
5235
|
|
|
5236
|
+
self.parent().highlight_overlay = n3d.binarize(target_data)
|
|
5237
|
+
else:
|
|
5238
|
+
self.parent().load_channel(accepted_mode, channel_data = target_data, data = True)
|
|
5239
|
+
except:
|
|
5240
|
+
pass
|
|
5057
5241
|
|
|
5058
5242
|
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5243
|
+
try:
|
|
5244
|
+
if accepted_target == 4:
|
|
5245
|
+
try:
|
|
5246
|
+
self.parent().highlight_overlay = n3d.binarize(active_data)
|
|
5247
|
+
except:
|
|
5248
|
+
self.parent().highlight_overlay = None
|
|
5249
|
+
except:
|
|
5250
|
+
pass
|
|
5064
5251
|
|
|
5065
5252
|
else:
|
|
5066
5253
|
self.parent().load_channel(accepted_target, channel_data = active_data, data = True)
|
|
@@ -5862,6 +6049,9 @@ class ResizeDialog(QDialog):
|
|
|
5862
6049
|
|
|
5863
6050
|
|
|
5864
6051
|
# Process highlight overlay if it exists
|
|
6052
|
+
if self.parent().mini_overlay_data is not None:
|
|
6053
|
+
self.parent().create_highlight_overlay(self.parent().clicked_values['nodes'], self.parent().clicked_values['edges'])
|
|
6054
|
+
|
|
5865
6055
|
if self.parent().highlight_overlay is not None:
|
|
5866
6056
|
self.parent().highlight_overlay = n3d.resize(self.parent().highlight_overlay, resize, order)
|
|
5867
6057
|
if my_network.search_region is not None:
|
|
@@ -5877,6 +6067,10 @@ class ResizeDialog(QDialog):
|
|
|
5877
6067
|
resized_data = n3d.upsample_with_padding(self.parent().channel_data[channel], original_shape = self.parent().original_shape)
|
|
5878
6068
|
self.parent().load_channel(channel, channel_data=resized_data, data=True, assign_shape = False)
|
|
5879
6069
|
|
|
6070
|
+
if self.parent().mini_overlay_data is not None:
|
|
6071
|
+
|
|
6072
|
+
self.parent().create_highlight_overlay(self.parent().clicked_values['nodes'], self.parent().clicked_values['edges'])
|
|
6073
|
+
|
|
5880
6074
|
|
|
5881
6075
|
# Process highlight overlay if it exists
|
|
5882
6076
|
if self.parent().highlight_overlay is not None:
|
|
@@ -6105,6 +6299,9 @@ class ThresholdDialog(QDialog):
|
|
|
6105
6299
|
if self.parent().volume_dict[self.parent().active_channel] is None:
|
|
6106
6300
|
self.parent().volumes()
|
|
6107
6301
|
|
|
6302
|
+
if self.parent().mini_overlay_data is not None:
|
|
6303
|
+
self.parent().mini_overlay_data = None
|
|
6304
|
+
|
|
6108
6305
|
thresh_window = ThresholdWindow(self.parent(), accepted_mode)
|
|
6109
6306
|
thresh_window.show() # Non-modal window
|
|
6110
6307
|
self.highlight_overlay = None
|
|
@@ -6128,6 +6325,8 @@ class ThresholdDialog(QDialog):
|
|
|
6128
6325
|
)
|
|
6129
6326
|
return
|
|
6130
6327
|
|
|
6328
|
+
if self.parent().mini_overlay_data is not None:
|
|
6329
|
+
self.parent().mini_overlay_data = None
|
|
6131
6330
|
|
|
6132
6331
|
self.parent().machine_window = MachineWindow(self.parent())
|
|
6133
6332
|
self.parent().machine_window.show() # Non-modal window
|
|
@@ -6240,7 +6439,7 @@ class MachineWindow(QMainWindow):
|
|
|
6240
6439
|
# Group 2: Processing Options (GPU)
|
|
6241
6440
|
processing_group = QGroupBox("Processing Options")
|
|
6242
6441
|
processing_layout = QHBoxLayout()
|
|
6243
|
-
self.GPU = QPushButton("GPU")
|
|
6442
|
+
self.GPU = QPushButton("GPU (Beta)")
|
|
6244
6443
|
self.GPU.setCheckable(True)
|
|
6245
6444
|
self.GPU.setChecked(False)
|
|
6246
6445
|
self.GPU.clicked.connect(self.toggle_GPU)
|
|
@@ -6276,9 +6475,15 @@ class MachineWindow(QMainWindow):
|
|
|
6276
6475
|
seg_button = QPushButton("Preview Segment")
|
|
6277
6476
|
self.seg_button = seg_button
|
|
6278
6477
|
seg_button.clicked.connect(self.start_segmentation)
|
|
6478
|
+
self.lock_button = QPushButton("🔒 Memory lock - (Prioritize RAM. Recommended unless you have a lot)")
|
|
6479
|
+
self.lock_button.setCheckable(True)
|
|
6480
|
+
self.lock_button.setChecked(True)
|
|
6481
|
+
self.lock_button.clicked.connect(self.toggle_lock)
|
|
6482
|
+
self.mem_lock = True
|
|
6279
6483
|
full_button = QPushButton("Segment All")
|
|
6280
6484
|
full_button.clicked.connect(self.segment)
|
|
6281
6485
|
segmentation_layout.addWidget(seg_button)
|
|
6486
|
+
segmentation_layout.addWidget(self.lock_button)
|
|
6282
6487
|
segmentation_layout.addWidget(full_button)
|
|
6283
6488
|
segmentation_group.setLayout(segmentation_layout)
|
|
6284
6489
|
|
|
@@ -6298,6 +6503,9 @@ class MachineWindow(QMainWindow):
|
|
|
6298
6503
|
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=True)
|
|
6299
6504
|
self.segmentation_worker = None
|
|
6300
6505
|
|
|
6506
|
+
def toggle_lock(self):
|
|
6507
|
+
|
|
6508
|
+
self.mem_lock = self.lock_button.isChecked()
|
|
6301
6509
|
|
|
6302
6510
|
|
|
6303
6511
|
def toggle_GPU(self):
|
|
@@ -6377,13 +6585,16 @@ class MachineWindow(QMainWindow):
|
|
|
6377
6585
|
if not self.use_two:
|
|
6378
6586
|
self.previewing = False
|
|
6379
6587
|
try:
|
|
6380
|
-
|
|
6381
|
-
|
|
6588
|
+
try:
|
|
6589
|
+
self.segmenter.train_batch(self.parent().channel_data[2], speed = speed, use_gpu = self.use_gpu, use_two = self.use_two, mem_lock = self.mem_lock)
|
|
6590
|
+
self.trained = True
|
|
6591
|
+
except Exception as e:
|
|
6592
|
+
print("Error training. Perhaps you forgot both foreground and background markers? I need both!")
|
|
6382
6593
|
except MemoryError:
|
|
6383
6594
|
QMessageBox.critical(
|
|
6384
6595
|
self,
|
|
6385
6596
|
"Alert",
|
|
6386
|
-
"Out of memory computing feature maps. Note these for 3D require 7x the RAM of the active image (or 9x for the detailed map).\n Please use 2D slice models if you do not have enough RAM."
|
|
6597
|
+
"Out of memory computing feature maps. Note these for 3D require 7x the RAM of the active image (or 9x for the detailed map).\n Please use 2D slice models or RAM lock if you do not have enough RAM."
|
|
6387
6598
|
)
|
|
6388
6599
|
|
|
6389
6600
|
|
|
@@ -6410,7 +6621,7 @@ class MachineWindow(QMainWindow):
|
|
|
6410
6621
|
if not self.trained:
|
|
6411
6622
|
return
|
|
6412
6623
|
else:
|
|
6413
|
-
self.segmentation_worker = SegmentationWorker(self.parent().highlight_overlay, self.segmenter, self.use_gpu, self.use_two, self.previewing, self)
|
|
6624
|
+
self.segmentation_worker = SegmentationWorker(self.parent().highlight_overlay, self.segmenter, self.use_gpu, self.use_two, self.previewing, self, self.mem_lock)
|
|
6414
6625
|
self.segmentation_worker.chunk_processed.connect(self.update_display) # Just update display
|
|
6415
6626
|
self.segmentation_worker.finished.connect(self.segmentation_finished)
|
|
6416
6627
|
current_xlim = self.parent().ax.get_xlim()
|
|
@@ -6647,7 +6858,7 @@ class SegmentationWorker(QThread):
|
|
|
6647
6858
|
finished = pyqtSignal()
|
|
6648
6859
|
chunk_processed = pyqtSignal()
|
|
6649
6860
|
|
|
6650
|
-
def __init__(self, highlight_overlay, segmenter, use_gpu, use_two, previewing, machine_window):
|
|
6861
|
+
def __init__(self, highlight_overlay, segmenter, use_gpu, use_two, previewing, machine_window, mem_lock):
|
|
6651
6862
|
super().__init__()
|
|
6652
6863
|
self.overlay = highlight_overlay
|
|
6653
6864
|
self.segmenter = segmenter
|
|
@@ -6655,6 +6866,7 @@ class SegmentationWorker(QThread):
|
|
|
6655
6866
|
self.use_two = use_two
|
|
6656
6867
|
self.previewing = previewing
|
|
6657
6868
|
self.machine_window = machine_window
|
|
6869
|
+
self.mem_lock = mem_lock
|
|
6658
6870
|
self._stop = False
|
|
6659
6871
|
self.update_interval = 1 # Increased to 500ms
|
|
6660
6872
|
self.chunks_since_update = 0
|
|
@@ -6774,6 +6986,7 @@ class ThresholdWindow(QMainWindow):
|
|
|
6774
6986
|
self.histo_list = self.parent().channel_data[self.parent().active_channel].flatten().tolist()
|
|
6775
6987
|
self.bounds = True
|
|
6776
6988
|
self.parent().bounds = True
|
|
6989
|
+
|
|
6777
6990
|
|
|
6778
6991
|
# Create matplotlib figure
|
|
6779
6992
|
fig = Figure(figsize=(5, 4))
|
|
@@ -7589,9 +7802,9 @@ class InvertDialog(QDialog):
|
|
|
7589
7802
|
if active_data.dtype == 'uint8' or 'int8':
|
|
7590
7803
|
num = 255
|
|
7591
7804
|
elif active_data.dtype == 'uint16' or 'int16':
|
|
7592
|
-
num =
|
|
7805
|
+
num = 65535
|
|
7593
7806
|
elif active_data.dtype == 'uint32' or 'int32':
|
|
7594
|
-
num =
|
|
7807
|
+
num = 2147483647
|
|
7595
7808
|
|
|
7596
7809
|
result = (num - active_data
|
|
7597
7810
|
)
|