nettracer3d 0.6.0__tar.gz → 0.6.1__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.
- {nettracer3d-0.6.0/src/nettracer3d.egg-info → nettracer3d-0.6.1}/PKG-INFO +7 -3
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/README.md +6 -2
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/pyproject.toml +1 -1
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/nettracer.py +0 -23
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/nettracer_gui.py +211 -21
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/segmenter.py +670 -54
- {nettracer3d-0.6.0 → nettracer3d-0.6.1/src/nettracer3d.egg-info}/PKG-INFO +7 -3
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d.egg-info/SOURCES.txt +0 -1
- nettracer3d-0.6.0/src/nettracer3d/hub_getter.py +0 -248
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/LICENSE +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/setup.cfg +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/__init__.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/community_extractor.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/modularity.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/morphology.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/network_analysis.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/network_draw.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/node_draw.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/proximity.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/run.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/simple_network.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d/smart_dilate.py +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d.egg-info/entry_points.txt +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/src/nettracer3d.egg-info/requires.txt +0 -0
- {nettracer3d-0.6.0 → nettracer3d-0.6.1}/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.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
|
|
5
5
|
Author-email: Liam McLaughlin <mclaughlinliam99@gmail.com>
|
|
6
6
|
Project-URL: User_Tutorial, https://www.youtube.com/watch?v=cRatn5VTWDY
|
|
@@ -44,6 +44,10 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
|
|
|
44
44
|
|
|
45
45
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
46
46
|
|
|
47
|
-
-- Version 0.6.
|
|
47
|
+
-- Version 0.6.1 updates --
|
|
48
48
|
|
|
49
|
-
1.
|
|
49
|
+
1. New feature for the machine learning segmenter. Now has a RAM lock mode which will always limit it to computing 1 chunk at a time, in both the interactive segmenter and the gross-segmenter. Feature map calculation within the chunk is made parallel to compensate which should allow this to function more optimally with RAM without really sacrificing performance. This should prevent the segmenter from majorly leaking memory in large arrays.
|
|
50
|
+
2. New function - 'Image' -> 'Select Objects'. Essentially just arbitrary selects/deselects lists of nodes or edges from the user in case there are some they are interested in but can't conveniently find. Allows imports from spreadsheets in case the user preorganizes some set of objects they want to select/deselect.
|
|
51
|
+
3. Brightness/Contrast now shades out of 65,535 instead of 255 which should allow better brightening options to images above 8bit depth.
|
|
52
|
+
4. Select all function updated to use the mini highlight overlay in larger images. Also reports the number of nodes/edges in the array in the cmd window when used.
|
|
53
|
+
5. Deleted the now unused 'hub_getter.py' script from the package.
|
|
@@ -8,6 +8,10 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
|
|
|
8
8
|
|
|
9
9
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
10
10
|
|
|
11
|
-
-- Version 0.6.
|
|
11
|
+
-- Version 0.6.1 updates --
|
|
12
12
|
|
|
13
|
-
1.
|
|
13
|
+
1. New feature for the machine learning segmenter. Now has a RAM lock mode which will always limit it to computing 1 chunk at a time, in both the interactive segmenter and the gross-segmenter. Feature map calculation within the chunk is made parallel to compensate which should allow this to function more optimally with RAM without really sacrificing performance. This should prevent the segmenter from majorly leaking memory in large arrays.
|
|
14
|
+
2. New function - 'Image' -> 'Select Objects'. Essentially just arbitrary selects/deselects lists of nodes or edges from the user in case there are some they are interested in but can't conveniently find. Allows imports from spreadsheets in case the user preorganizes some set of objects they want to select/deselect.
|
|
15
|
+
3. Brightness/Contrast now shades out of 65,535 instead of 255 which should allow better brightening options to images above 8bit depth.
|
|
16
|
+
4. Select all function updated to use the mini highlight overlay in larger images. Also reports the number of nodes/edges in the array in the cmd window when used.
|
|
17
|
+
5. Deleted the now unused 'hub_getter.py' script from the package.
|
|
@@ -27,7 +27,6 @@ from skimage import morphology as mpg
|
|
|
27
27
|
from . import smart_dilate
|
|
28
28
|
from . import modularity
|
|
29
29
|
from . import simple_network
|
|
30
|
-
from . import hub_getter
|
|
31
30
|
from . import community_extractor
|
|
32
31
|
from . import network_analysis
|
|
33
32
|
from . import morphology
|
|
@@ -3934,28 +3933,6 @@ class Network_3D:
|
|
|
3934
3933
|
|
|
3935
3934
|
return degrees, nodes
|
|
3936
3935
|
|
|
3937
|
-
def get_hubs(self, proportion = None, down_factor = 1, directory = None):
|
|
3938
|
-
"""
|
|
3939
|
-
Method to isolate hub regions of a network (Removing all nodes below some proportion of highest degrees), also generating overlays that relate this information to the 3D structure.
|
|
3940
|
-
Overlays include a grayscale image where nodes are assigned a grayscale value corresponding to their degree, and a numerical index where numbers are drawn at nodes corresponding to their degree.
|
|
3941
|
-
These will be saved to the active directory if none is specified. Note calculations will be done with node_centroids unless a down_factor is passed. Note that a down_factor must be passed if there are no node_centroids.
|
|
3942
|
-
:param proportion: (Optional - Val = None; Float). A float of 0 to 1 that details what proportion of highest node degrees to include in the output. Note that this value will be set to 0.1 by default.
|
|
3943
|
-
:param down_factor: (Optional - Val = 1; int). A factor to downsample nodes by while calculating centroids, assuming no node_centroids property was set.
|
|
3944
|
-
:param directory: (Optional - Val = None; string). A path to a directory to save outputs.
|
|
3945
|
-
:returns: A dictionary of degree values for each node above the desired proportion of highest degree nodes.
|
|
3946
|
-
"""
|
|
3947
|
-
if down_factor > 1:
|
|
3948
|
-
centroids = self._node_centroids.copy()
|
|
3949
|
-
for item in self._node_centroids:
|
|
3950
|
-
centroids[item] = np.round((self._node_centroids[item]) / down_factor)
|
|
3951
|
-
nodes = downsample(self._nodes, down_factor)
|
|
3952
|
-
hubs = hub_getter.get_hubs(nodes, self._network, proportion, directory = directory, centroids = centroids)
|
|
3953
|
-
|
|
3954
|
-
else:
|
|
3955
|
-
hubs = hub_getter.get_hubs(self._nodes, self._network, proportion, directory = directory, centroids = self._node_centroids)
|
|
3956
|
-
|
|
3957
|
-
return hubs
|
|
3958
|
-
|
|
3959
3936
|
|
|
3960
3937
|
def isolate_connected_component(self, key = None, directory = None, full_edges = None, gen_images = True):
|
|
3961
3938
|
"""
|
|
@@ -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
|
|
|
@@ -4636,14 +4652,14 @@ class BrightnessContrastDialog(QDialog):
|
|
|
4636
4652
|
# Create range slider
|
|
4637
4653
|
slider = QRangeSlider(Qt.Orientation.Horizontal)
|
|
4638
4654
|
slider.setMinimum(0)
|
|
4639
|
-
slider.setMaximum(
|
|
4640
|
-
slider.setValue((0,
|
|
4655
|
+
slider.setMaximum(65535)
|
|
4656
|
+
slider.setValue((0, 65535))
|
|
4641
4657
|
self.brightness_sliders.append(slider)
|
|
4642
4658
|
|
|
4643
4659
|
# Create max value input
|
|
4644
4660
|
max_input = QLineEdit()
|
|
4645
4661
|
max_input.setFixedWidth(50)
|
|
4646
|
-
max_input.setText("
|
|
4662
|
+
max_input.setText("65535")
|
|
4647
4663
|
self.max_inputs.append(max_input)
|
|
4648
4664
|
|
|
4649
4665
|
# Add all components to slider container
|
|
@@ -4692,8 +4708,8 @@ class BrightnessContrastDialog(QDialog):
|
|
|
4692
4708
|
max_val = self.parse_input_value(self.max_inputs[channel].text())
|
|
4693
4709
|
current_min, current_max = self.brightness_sliders[channel].value()
|
|
4694
4710
|
|
|
4695
|
-
if max_val >
|
|
4696
|
-
max_val =
|
|
4711
|
+
if max_val > 65535:
|
|
4712
|
+
max_val = 65535
|
|
4697
4713
|
# Ensure max doesn't go below min
|
|
4698
4714
|
max_val = max(max_val, current_min + 1)
|
|
4699
4715
|
|
|
@@ -4713,8 +4729,8 @@ class BrightnessContrastDialog(QDialog):
|
|
|
4713
4729
|
value = float(text)
|
|
4714
4730
|
# Round to nearest integer
|
|
4715
4731
|
value = int(round(value))
|
|
4716
|
-
# Clamp between 0 and
|
|
4717
|
-
return max(0, min(
|
|
4732
|
+
# Clamp between 0 and 65535
|
|
4733
|
+
return max(0, min(65535, value))
|
|
4718
4734
|
except ValueError:
|
|
4719
4735
|
raise ValueError("Invalid input")
|
|
4720
4736
|
|
|
@@ -4761,7 +4777,168 @@ class ColorDialog(QDialog):
|
|
|
4761
4777
|
|
|
4762
4778
|
# Update the display
|
|
4763
4779
|
self.parent().update_display()
|
|
4764
|
-
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)}")
|
|
4765
4942
|
|
|
4766
4943
|
class Show3dDialog(QDialog):
|
|
4767
4944
|
def __init__(self, parent=None):
|
|
@@ -6262,7 +6439,7 @@ class MachineWindow(QMainWindow):
|
|
|
6262
6439
|
# Group 2: Processing Options (GPU)
|
|
6263
6440
|
processing_group = QGroupBox("Processing Options")
|
|
6264
6441
|
processing_layout = QHBoxLayout()
|
|
6265
|
-
self.GPU = QPushButton("GPU")
|
|
6442
|
+
self.GPU = QPushButton("GPU (Beta)")
|
|
6266
6443
|
self.GPU.setCheckable(True)
|
|
6267
6444
|
self.GPU.setChecked(False)
|
|
6268
6445
|
self.GPU.clicked.connect(self.toggle_GPU)
|
|
@@ -6298,9 +6475,15 @@ class MachineWindow(QMainWindow):
|
|
|
6298
6475
|
seg_button = QPushButton("Preview Segment")
|
|
6299
6476
|
self.seg_button = seg_button
|
|
6300
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
|
|
6301
6483
|
full_button = QPushButton("Segment All")
|
|
6302
6484
|
full_button.clicked.connect(self.segment)
|
|
6303
6485
|
segmentation_layout.addWidget(seg_button)
|
|
6486
|
+
segmentation_layout.addWidget(self.lock_button)
|
|
6304
6487
|
segmentation_layout.addWidget(full_button)
|
|
6305
6488
|
segmentation_group.setLayout(segmentation_layout)
|
|
6306
6489
|
|
|
@@ -6320,6 +6503,9 @@ class MachineWindow(QMainWindow):
|
|
|
6320
6503
|
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=True)
|
|
6321
6504
|
self.segmentation_worker = None
|
|
6322
6505
|
|
|
6506
|
+
def toggle_lock(self):
|
|
6507
|
+
|
|
6508
|
+
self.mem_lock = self.lock_button.isChecked()
|
|
6323
6509
|
|
|
6324
6510
|
|
|
6325
6511
|
def toggle_GPU(self):
|
|
@@ -6399,13 +6585,16 @@ class MachineWindow(QMainWindow):
|
|
|
6399
6585
|
if not self.use_two:
|
|
6400
6586
|
self.previewing = False
|
|
6401
6587
|
try:
|
|
6402
|
-
|
|
6403
|
-
|
|
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!")
|
|
6404
6593
|
except MemoryError:
|
|
6405
6594
|
QMessageBox.critical(
|
|
6406
6595
|
self,
|
|
6407
6596
|
"Alert",
|
|
6408
|
-
"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."
|
|
6409
6598
|
)
|
|
6410
6599
|
|
|
6411
6600
|
|
|
@@ -6432,7 +6621,7 @@ class MachineWindow(QMainWindow):
|
|
|
6432
6621
|
if not self.trained:
|
|
6433
6622
|
return
|
|
6434
6623
|
else:
|
|
6435
|
-
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)
|
|
6436
6625
|
self.segmentation_worker.chunk_processed.connect(self.update_display) # Just update display
|
|
6437
6626
|
self.segmentation_worker.finished.connect(self.segmentation_finished)
|
|
6438
6627
|
current_xlim = self.parent().ax.get_xlim()
|
|
@@ -6669,7 +6858,7 @@ class SegmentationWorker(QThread):
|
|
|
6669
6858
|
finished = pyqtSignal()
|
|
6670
6859
|
chunk_processed = pyqtSignal()
|
|
6671
6860
|
|
|
6672
|
-
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):
|
|
6673
6862
|
super().__init__()
|
|
6674
6863
|
self.overlay = highlight_overlay
|
|
6675
6864
|
self.segmenter = segmenter
|
|
@@ -6677,6 +6866,7 @@ class SegmentationWorker(QThread):
|
|
|
6677
6866
|
self.use_two = use_two
|
|
6678
6867
|
self.previewing = previewing
|
|
6679
6868
|
self.machine_window = machine_window
|
|
6869
|
+
self.mem_lock = mem_lock
|
|
6680
6870
|
self._stop = False
|
|
6681
6871
|
self.update_interval = 1 # Increased to 500ms
|
|
6682
6872
|
self.chunks_since_update = 0
|
|
@@ -7612,9 +7802,9 @@ class InvertDialog(QDialog):
|
|
|
7612
7802
|
if active_data.dtype == 'uint8' or 'int8':
|
|
7613
7803
|
num = 255
|
|
7614
7804
|
elif active_data.dtype == 'uint16' or 'int16':
|
|
7615
|
-
num =
|
|
7805
|
+
num = 65535
|
|
7616
7806
|
elif active_data.dtype == 'uint32' or 'int32':
|
|
7617
|
-
num =
|
|
7807
|
+
num = 2147483647
|
|
7618
7808
|
|
|
7619
7809
|
result = (num - active_data
|
|
7620
7810
|
)
|