lazylabel-gui 1.1.1__py3-none-any.whl → 1.1.2__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.
- lazylabel/__init__.py +8 -8
- lazylabel/config/__init__.py +6 -6
- lazylabel/config/hotkeys.py +168 -168
- lazylabel/config/paths.py +40 -40
- lazylabel/config/settings.py +65 -65
- lazylabel/core/__init__.py +6 -6
- lazylabel/core/file_manager.py +105 -105
- lazylabel/core/model_manager.py +97 -97
- lazylabel/core/segment_manager.py +171 -171
- lazylabel/main.py +36 -36
- lazylabel/models/__init__.py +4 -4
- lazylabel/models/sam_model.py +195 -195
- lazylabel/ui/__init__.py +7 -7
- lazylabel/ui/control_panel.py +241 -237
- lazylabel/ui/editable_vertex.py +64 -51
- lazylabel/ui/hotkey_dialog.py +383 -383
- lazylabel/ui/hoverable_pixelmap_item.py +22 -22
- lazylabel/ui/hoverable_polygon_item.py +39 -39
- lazylabel/ui/main_window.py +1659 -1546
- lazylabel/ui/numeric_table_widget_item.py +9 -9
- lazylabel/ui/photo_viewer.py +54 -54
- lazylabel/ui/reorderable_class_table.py +61 -61
- lazylabel/ui/right_panel.py +315 -315
- lazylabel/ui/widgets/__init__.py +8 -8
- lazylabel/ui/widgets/adjustments_widget.py +108 -107
- lazylabel/ui/widgets/model_selection_widget.py +93 -93
- lazylabel/ui/widgets/settings_widget.py +105 -105
- lazylabel/ui/widgets/status_bar.py +109 -109
- lazylabel/utils/__init__.py +5 -5
- lazylabel/utils/custom_file_system_model.py +132 -132
- lazylabel/utils/utils.py +12 -12
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.2.dist-info}/METADATA +197 -197
- lazylabel_gui-1.1.2.dist-info/RECORD +37 -0
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.2.dist-info}/licenses/LICENSE +21 -21
- lazylabel_gui-1.1.1.dist-info/RECORD +0 -37
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.2.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.2.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.2.dist-info}/top_level.txt +0 -0
@@ -1,107 +1,108 @@
|
|
1
|
-
"""Adjustments widget for sliders and controls."""
|
2
|
-
|
3
|
-
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QSlider, QGroupBox
|
4
|
-
from PyQt6.QtCore import Qt, pyqtSignal
|
5
|
-
|
6
|
-
|
7
|
-
class AdjustmentsWidget(QWidget):
|
8
|
-
"""Widget for adjustment controls."""
|
9
|
-
|
10
|
-
annotation_size_changed = pyqtSignal(int)
|
11
|
-
pan_speed_changed = pyqtSignal(int)
|
12
|
-
join_threshold_changed = pyqtSignal(int)
|
13
|
-
|
14
|
-
def __init__(self, parent=None):
|
15
|
-
super().__init__(parent)
|
16
|
-
self._setup_ui()
|
17
|
-
self._connect_signals()
|
18
|
-
|
19
|
-
def _setup_ui(self):
|
20
|
-
"""Setup the UI layout."""
|
21
|
-
group = QGroupBox("Adjustments")
|
22
|
-
layout = QVBoxLayout(group)
|
23
|
-
|
24
|
-
# Annotation size
|
25
|
-
self.size_label = QLabel("Annotation Size: 1.0x")
|
26
|
-
self.size_slider = QSlider(Qt.Orientation.Horizontal)
|
27
|
-
self.size_slider.setRange(1, 50)
|
28
|
-
self.size_slider.setValue(10)
|
29
|
-
self.size_slider.setToolTip("Adjusts the size of points and lines (Ctrl +/-)")
|
30
|
-
layout.addWidget(self.size_label)
|
31
|
-
layout.addWidget(self.size_slider)
|
32
|
-
|
33
|
-
layout.addSpacing(10)
|
34
|
-
|
35
|
-
# Pan speed
|
36
|
-
self.pan_label = QLabel("Pan Speed: 1.0x")
|
37
|
-
self.pan_slider = QSlider(Qt.Orientation.Horizontal)
|
38
|
-
self.pan_slider.setRange(1, 100)
|
39
|
-
self.pan_slider.setValue(10)
|
40
|
-
self.pan_slider.setToolTip(
|
41
|
-
"Adjusts the speed of WASD panning. Hold Shift for 5x boost."
|
42
|
-
)
|
43
|
-
layout.addWidget(self.pan_label)
|
44
|
-
layout.addWidget(self.pan_slider)
|
45
|
-
|
46
|
-
layout.addSpacing(10)
|
47
|
-
|
48
|
-
# Polygon join threshold
|
49
|
-
self.join_label = QLabel("Polygon Join Distance: 2px")
|
50
|
-
self.join_slider = QSlider(Qt.Orientation.Horizontal)
|
51
|
-
self.join_slider.setRange(1, 10)
|
52
|
-
self.join_slider.setValue(2)
|
53
|
-
self.join_slider.setToolTip("The pixel distance to 'snap' a polygon closed.")
|
54
|
-
layout.addWidget(self.join_label)
|
55
|
-
layout.addWidget(self.join_slider)
|
56
|
-
|
57
|
-
# Main layout
|
58
|
-
main_layout = QVBoxLayout(self)
|
59
|
-
main_layout.setContentsMargins(0, 0, 0, 0)
|
60
|
-
main_layout.addWidget(group)
|
61
|
-
|
62
|
-
def _connect_signals(self):
|
63
|
-
"""Connect internal signals."""
|
64
|
-
self.size_slider.valueChanged.connect(self._on_size_changed)
|
65
|
-
self.pan_slider.valueChanged.connect(self._on_pan_changed)
|
66
|
-
self.join_slider.valueChanged.connect(self._on_join_changed)
|
67
|
-
|
68
|
-
def _on_size_changed(self, value):
|
69
|
-
"""Handle annotation size change."""
|
70
|
-
multiplier = value / 10.0
|
71
|
-
self.size_label.setText(f"Annotation Size: {multiplier:.1f}x")
|
72
|
-
self.annotation_size_changed.emit(value)
|
73
|
-
|
74
|
-
def _on_pan_changed(self, value):
|
75
|
-
"""Handle pan speed change."""
|
76
|
-
multiplier = value / 10.0
|
77
|
-
self.pan_label.setText(f"Pan Speed: {multiplier:.1f}x")
|
78
|
-
self.pan_speed_changed.emit(value)
|
79
|
-
|
80
|
-
def _on_join_changed(self, value):
|
81
|
-
"""Handle join threshold change."""
|
82
|
-
self.join_label.setText(f"Polygon Join Distance: {value}px")
|
83
|
-
self.join_threshold_changed.emit(value)
|
84
|
-
|
85
|
-
def get_annotation_size(self):
|
86
|
-
"""Get current annotation size value."""
|
87
|
-
return self.size_slider.value()
|
88
|
-
|
89
|
-
def set_annotation_size(self, value):
|
90
|
-
"""Set annotation size value."""
|
91
|
-
self.size_slider.setValue(value)
|
92
|
-
|
93
|
-
def get_pan_speed(self):
|
94
|
-
"""Get current pan speed value."""
|
95
|
-
return self.pan_slider.value()
|
96
|
-
|
97
|
-
def set_pan_speed(self, value):
|
98
|
-
"""Set pan speed value."""
|
99
|
-
self.pan_slider.setValue(value)
|
100
|
-
|
101
|
-
def get_join_threshold(self):
|
102
|
-
"""Get current join threshold value."""
|
103
|
-
return self.join_slider.value()
|
104
|
-
|
105
|
-
def set_join_threshold(self, value):
|
106
|
-
"""Set join threshold value."""
|
107
|
-
self.join_slider.setValue(value)
|
1
|
+
"""Adjustments widget for sliders and controls."""
|
2
|
+
|
3
|
+
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QSlider, QGroupBox
|
4
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
5
|
+
|
6
|
+
|
7
|
+
class AdjustmentsWidget(QWidget):
|
8
|
+
"""Widget for adjustment controls."""
|
9
|
+
|
10
|
+
annotation_size_changed = pyqtSignal(int)
|
11
|
+
pan_speed_changed = pyqtSignal(int)
|
12
|
+
join_threshold_changed = pyqtSignal(int)
|
13
|
+
|
14
|
+
def __init__(self, parent=None):
|
15
|
+
super().__init__(parent)
|
16
|
+
self._setup_ui()
|
17
|
+
self._connect_signals()
|
18
|
+
|
19
|
+
def _setup_ui(self):
|
20
|
+
"""Setup the UI layout."""
|
21
|
+
group = QGroupBox("Adjustments")
|
22
|
+
layout = QVBoxLayout(group)
|
23
|
+
|
24
|
+
# Annotation size
|
25
|
+
self.size_label = QLabel("Annotation Size: 1.0x")
|
26
|
+
self.size_slider = QSlider(Qt.Orientation.Horizontal)
|
27
|
+
self.size_slider.setRange(1, 50)
|
28
|
+
self.size_slider.setValue(10)
|
29
|
+
self.size_slider.setToolTip("Adjusts the size of points and lines (Ctrl +/-)")
|
30
|
+
layout.addWidget(self.size_label)
|
31
|
+
layout.addWidget(self.size_slider)
|
32
|
+
|
33
|
+
layout.addSpacing(10)
|
34
|
+
|
35
|
+
# Pan speed
|
36
|
+
self.pan_label = QLabel("Pan Speed: 1.0x")
|
37
|
+
self.pan_slider = QSlider(Qt.Orientation.Horizontal)
|
38
|
+
self.pan_slider.setRange(1, 100)
|
39
|
+
self.pan_slider.setValue(10)
|
40
|
+
self.pan_slider.setToolTip(
|
41
|
+
"Adjusts the speed of WASD panning. Hold Shift for 5x boost."
|
42
|
+
)
|
43
|
+
layout.addWidget(self.pan_label)
|
44
|
+
layout.addWidget(self.pan_slider)
|
45
|
+
|
46
|
+
layout.addSpacing(10)
|
47
|
+
|
48
|
+
# Polygon join threshold
|
49
|
+
self.join_label = QLabel("Polygon Join Distance: 2px")
|
50
|
+
self.join_slider = QSlider(Qt.Orientation.Horizontal)
|
51
|
+
self.join_slider.setRange(1, 10)
|
52
|
+
self.join_slider.setValue(2)
|
53
|
+
self.join_slider.setToolTip("The pixel distance to 'snap' a polygon closed.")
|
54
|
+
layout.addWidget(self.join_label)
|
55
|
+
layout.addWidget(self.join_slider)
|
56
|
+
|
57
|
+
# Main layout
|
58
|
+
main_layout = QVBoxLayout(self)
|
59
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
60
|
+
main_layout.addWidget(group)
|
61
|
+
|
62
|
+
def _connect_signals(self):
|
63
|
+
"""Connect internal signals."""
|
64
|
+
self.size_slider.valueChanged.connect(self._on_size_changed)
|
65
|
+
self.pan_slider.valueChanged.connect(self._on_pan_changed)
|
66
|
+
self.join_slider.valueChanged.connect(self._on_join_changed)
|
67
|
+
|
68
|
+
def _on_size_changed(self, value):
|
69
|
+
"""Handle annotation size change."""
|
70
|
+
multiplier = value / 10.0
|
71
|
+
self.size_label.setText(f"Annotation Size: {multiplier:.1f}x")
|
72
|
+
self.annotation_size_changed.emit(value)
|
73
|
+
|
74
|
+
def _on_pan_changed(self, value):
|
75
|
+
"""Handle pan speed change."""
|
76
|
+
multiplier = value / 10.0
|
77
|
+
self.pan_label.setText(f"Pan Speed: {multiplier:.1f}x")
|
78
|
+
self.pan_speed_changed.emit(value)
|
79
|
+
|
80
|
+
def _on_join_changed(self, value):
|
81
|
+
"""Handle join threshold change."""
|
82
|
+
self.join_label.setText(f"Polygon Join Distance: {value}px")
|
83
|
+
self.join_threshold_changed.emit(value)
|
84
|
+
|
85
|
+
def get_annotation_size(self):
|
86
|
+
"""Get current annotation size value."""
|
87
|
+
return self.size_slider.value()
|
88
|
+
|
89
|
+
def set_annotation_size(self, value):
|
90
|
+
"""Set annotation size value."""
|
91
|
+
self.size_slider.setValue(value)
|
92
|
+
|
93
|
+
def get_pan_speed(self):
|
94
|
+
"""Get current pan speed value."""
|
95
|
+
return self.pan_slider.value()
|
96
|
+
|
97
|
+
def set_pan_speed(self, value):
|
98
|
+
"""Set pan speed value."""
|
99
|
+
self.pan_slider.setValue(value)
|
100
|
+
|
101
|
+
def get_join_threshold(self):
|
102
|
+
"""Get current join threshold value."""
|
103
|
+
return self.join_slider.value()
|
104
|
+
|
105
|
+
def set_join_threshold(self, value):
|
106
|
+
"""Set join threshold value."""
|
107
|
+
self.join_slider.setValue(value)
|
108
|
+
self.join_label.setText(f"Polygon Join Distance: {value}px")
|
@@ -1,94 +1,94 @@
|
|
1
|
-
"""Model selection widget."""
|
2
|
-
|
3
|
-
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QComboBox, QGroupBox
|
4
|
-
from PyQt6.QtCore import pyqtSignal
|
5
|
-
from typing import List, Tuple
|
6
|
-
|
7
|
-
|
8
|
-
class ModelSelectionWidget(QWidget):
|
9
|
-
"""Widget for model selection and management."""
|
10
|
-
|
11
|
-
browse_requested = pyqtSignal()
|
12
|
-
refresh_requested = pyqtSignal()
|
13
|
-
model_selected = pyqtSignal(str)
|
14
|
-
|
15
|
-
def __init__(self, parent=None):
|
16
|
-
super().__init__(parent)
|
17
|
-
self._setup_ui()
|
18
|
-
self._connect_signals()
|
19
|
-
|
20
|
-
def _setup_ui(self):
|
21
|
-
"""Setup the UI layout."""
|
22
|
-
group = QGroupBox("Model Selection")
|
23
|
-
layout = QVBoxLayout(group)
|
24
|
-
|
25
|
-
# Buttons
|
26
|
-
button_layout = QHBoxLayout()
|
27
|
-
self.btn_browse = QPushButton("Browse Models")
|
28
|
-
self.btn_browse.setToolTip("Browse for a folder containing .pth model files")
|
29
|
-
self.btn_refresh = QPushButton("Refresh")
|
30
|
-
self.btn_refresh.setToolTip("Refresh the list of available models")
|
31
|
-
|
32
|
-
button_layout.addWidget(self.btn_browse)
|
33
|
-
button_layout.addWidget(self.btn_refresh)
|
34
|
-
layout.addLayout(button_layout)
|
35
|
-
|
36
|
-
# Model combo
|
37
|
-
layout.addWidget(QLabel("Available Models:"))
|
38
|
-
self.model_combo = QComboBox()
|
39
|
-
self.model_combo.setToolTip("Select a .pth model file to use")
|
40
|
-
self.model_combo.addItem("Default (vit_h)")
|
41
|
-
layout.addWidget(self.model_combo)
|
42
|
-
|
43
|
-
# Current model label
|
44
|
-
self.current_model_label = QLabel("Current: Default SAM Model")
|
45
|
-
self.current_model_label.setWordWrap(True)
|
46
|
-
self.current_model_label.setStyleSheet("color: #90EE90; font-style: italic;")
|
47
|
-
layout.addWidget(self.current_model_label)
|
48
|
-
|
49
|
-
# Main layout
|
50
|
-
main_layout = QVBoxLayout(self)
|
51
|
-
main_layout.setContentsMargins(0, 0, 0, 0)
|
52
|
-
main_layout.addWidget(group)
|
53
|
-
|
54
|
-
def _connect_signals(self):
|
55
|
-
"""Connect internal signals."""
|
56
|
-
self.btn_browse.clicked.connect(self.browse_requested)
|
57
|
-
self.btn_refresh.clicked.connect(self.refresh_requested)
|
58
|
-
self.model_combo.currentTextChanged.connect(self.model_selected)
|
59
|
-
|
60
|
-
def populate_models(self, models: List[Tuple[str, str]]):
|
61
|
-
"""Populate the models combo box.
|
62
|
-
|
63
|
-
Args:
|
64
|
-
models: List of (display_name, full_path) tuples
|
65
|
-
"""
|
66
|
-
self.model_combo.blockSignals(True)
|
67
|
-
self.model_combo.clear()
|
68
|
-
|
69
|
-
# Add default option
|
70
|
-
self.model_combo.addItem("Default (vit_h)")
|
71
|
-
|
72
|
-
# Add custom models
|
73
|
-
for display_name, full_path in models:
|
74
|
-
self.model_combo.addItem(display_name, full_path)
|
75
|
-
|
76
|
-
self.model_combo.blockSignals(False)
|
77
|
-
|
78
|
-
def set_current_model(self, model_name: str):
|
79
|
-
"""Set the current model display."""
|
80
|
-
self.current_model_label.setText(model_name)
|
81
|
-
|
82
|
-
def get_selected_model_path(self) -> str:
|
83
|
-
"""Get the path of the currently selected model."""
|
84
|
-
current_index = self.model_combo.currentIndex()
|
85
|
-
if current_index <= 0: # Default option
|
86
|
-
return ""
|
87
|
-
return self.model_combo.itemData(current_index) or ""
|
88
|
-
|
89
|
-
def reset_to_default(self):
|
90
|
-
"""Reset selection to default model."""
|
91
|
-
self.model_combo.blockSignals(True)
|
92
|
-
self.model_combo.setCurrentIndex(0)
|
93
|
-
self.model_combo.blockSignals(False)
|
1
|
+
"""Model selection widget."""
|
2
|
+
|
3
|
+
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QComboBox, QGroupBox
|
4
|
+
from PyQt6.QtCore import pyqtSignal
|
5
|
+
from typing import List, Tuple
|
6
|
+
|
7
|
+
|
8
|
+
class ModelSelectionWidget(QWidget):
|
9
|
+
"""Widget for model selection and management."""
|
10
|
+
|
11
|
+
browse_requested = pyqtSignal()
|
12
|
+
refresh_requested = pyqtSignal()
|
13
|
+
model_selected = pyqtSignal(str)
|
14
|
+
|
15
|
+
def __init__(self, parent=None):
|
16
|
+
super().__init__(parent)
|
17
|
+
self._setup_ui()
|
18
|
+
self._connect_signals()
|
19
|
+
|
20
|
+
def _setup_ui(self):
|
21
|
+
"""Setup the UI layout."""
|
22
|
+
group = QGroupBox("Model Selection")
|
23
|
+
layout = QVBoxLayout(group)
|
24
|
+
|
25
|
+
# Buttons
|
26
|
+
button_layout = QHBoxLayout()
|
27
|
+
self.btn_browse = QPushButton("Browse Models")
|
28
|
+
self.btn_browse.setToolTip("Browse for a folder containing .pth model files")
|
29
|
+
self.btn_refresh = QPushButton("Refresh")
|
30
|
+
self.btn_refresh.setToolTip("Refresh the list of available models")
|
31
|
+
|
32
|
+
button_layout.addWidget(self.btn_browse)
|
33
|
+
button_layout.addWidget(self.btn_refresh)
|
34
|
+
layout.addLayout(button_layout)
|
35
|
+
|
36
|
+
# Model combo
|
37
|
+
layout.addWidget(QLabel("Available Models:"))
|
38
|
+
self.model_combo = QComboBox()
|
39
|
+
self.model_combo.setToolTip("Select a .pth model file to use")
|
40
|
+
self.model_combo.addItem("Default (vit_h)")
|
41
|
+
layout.addWidget(self.model_combo)
|
42
|
+
|
43
|
+
# Current model label
|
44
|
+
self.current_model_label = QLabel("Current: Default SAM Model")
|
45
|
+
self.current_model_label.setWordWrap(True)
|
46
|
+
self.current_model_label.setStyleSheet("color: #90EE90; font-style: italic;")
|
47
|
+
layout.addWidget(self.current_model_label)
|
48
|
+
|
49
|
+
# Main layout
|
50
|
+
main_layout = QVBoxLayout(self)
|
51
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
52
|
+
main_layout.addWidget(group)
|
53
|
+
|
54
|
+
def _connect_signals(self):
|
55
|
+
"""Connect internal signals."""
|
56
|
+
self.btn_browse.clicked.connect(self.browse_requested)
|
57
|
+
self.btn_refresh.clicked.connect(self.refresh_requested)
|
58
|
+
self.model_combo.currentTextChanged.connect(self.model_selected)
|
59
|
+
|
60
|
+
def populate_models(self, models: List[Tuple[str, str]]):
|
61
|
+
"""Populate the models combo box.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
models: List of (display_name, full_path) tuples
|
65
|
+
"""
|
66
|
+
self.model_combo.blockSignals(True)
|
67
|
+
self.model_combo.clear()
|
68
|
+
|
69
|
+
# Add default option
|
70
|
+
self.model_combo.addItem("Default (vit_h)")
|
71
|
+
|
72
|
+
# Add custom models
|
73
|
+
for display_name, full_path in models:
|
74
|
+
self.model_combo.addItem(display_name, full_path)
|
75
|
+
|
76
|
+
self.model_combo.blockSignals(False)
|
77
|
+
|
78
|
+
def set_current_model(self, model_name: str):
|
79
|
+
"""Set the current model display."""
|
80
|
+
self.current_model_label.setText(model_name)
|
81
|
+
|
82
|
+
def get_selected_model_path(self) -> str:
|
83
|
+
"""Get the path of the currently selected model."""
|
84
|
+
current_index = self.model_combo.currentIndex()
|
85
|
+
if current_index <= 0: # Default option
|
86
|
+
return ""
|
87
|
+
return self.model_combo.itemData(current_index) or ""
|
88
|
+
|
89
|
+
def reset_to_default(self):
|
90
|
+
"""Reset selection to default model."""
|
91
|
+
self.model_combo.blockSignals(True)
|
92
|
+
self.model_combo.setCurrentIndex(0)
|
93
|
+
self.model_combo.blockSignals(False)
|
94
94
|
self.set_current_model("Current: Default SAM Model")
|
@@ -1,106 +1,106 @@
|
|
1
|
-
"""Settings widget for save options."""
|
2
|
-
|
3
|
-
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QGroupBox
|
4
|
-
from PyQt6.QtCore import pyqtSignal
|
5
|
-
|
6
|
-
|
7
|
-
class SettingsWidget(QWidget):
|
8
|
-
"""Widget for application settings."""
|
9
|
-
|
10
|
-
settings_changed = pyqtSignal()
|
11
|
-
|
12
|
-
def __init__(self, parent=None):
|
13
|
-
super().__init__(parent)
|
14
|
-
self._setup_ui()
|
15
|
-
self._connect_signals()
|
16
|
-
|
17
|
-
def _setup_ui(self):
|
18
|
-
"""Setup the UI layout."""
|
19
|
-
group = QGroupBox("Settings")
|
20
|
-
layout = QVBoxLayout(group)
|
21
|
-
|
22
|
-
# Auto-save
|
23
|
-
self.chk_auto_save = QCheckBox("Auto-Save on Navigate")
|
24
|
-
self.chk_auto_save.setToolTip(
|
25
|
-
"Automatically save work when using arrow keys to change images."
|
26
|
-
)
|
27
|
-
self.chk_auto_save.setChecked(True)
|
28
|
-
layout.addWidget(self.chk_auto_save)
|
29
|
-
|
30
|
-
# Save NPZ
|
31
|
-
self.chk_save_npz = QCheckBox("Save .npz")
|
32
|
-
self.chk_save_npz.setChecked(True)
|
33
|
-
self.chk_save_npz.setToolTip(
|
34
|
-
"Save the final mask as a compressed NumPy NPZ file."
|
35
|
-
)
|
36
|
-
layout.addWidget(self.chk_save_npz)
|
37
|
-
|
38
|
-
# Save TXT
|
39
|
-
self.chk_save_txt = QCheckBox("Save .txt")
|
40
|
-
self.chk_save_txt.setChecked(True)
|
41
|
-
self.chk_save_txt.setToolTip(
|
42
|
-
"Save bounding box annotations in YOLO TXT format."
|
43
|
-
)
|
44
|
-
layout.addWidget(self.chk_save_txt)
|
45
|
-
|
46
|
-
# YOLO with aliases
|
47
|
-
self.chk_yolo_use_alias = QCheckBox("Save YOLO with Class Aliases")
|
48
|
-
self.chk_yolo_use_alias.setToolTip(
|
49
|
-
"If checked, saves YOLO .txt files using class alias names instead of numeric IDs.\n"
|
50
|
-
"This is useful when a separate .yaml or .names file defines the classes."
|
51
|
-
)
|
52
|
-
self.chk_yolo_use_alias.setChecked(True)
|
53
|
-
layout.addWidget(self.chk_yolo_use_alias)
|
54
|
-
|
55
|
-
# Save class aliases
|
56
|
-
self.chk_save_class_aliases = QCheckBox("Save Class Aliases (.json)")
|
57
|
-
self.chk_save_class_aliases.setToolTip(
|
58
|
-
"Save class aliases to a companion JSON file."
|
59
|
-
)
|
60
|
-
self.chk_save_class_aliases.setChecked(False)
|
61
|
-
layout.addWidget(self.chk_save_class_aliases)
|
62
|
-
|
63
|
-
# Main layout
|
64
|
-
main_layout = QVBoxLayout(self)
|
65
|
-
main_layout.setContentsMargins(0, 0, 0, 0)
|
66
|
-
main_layout.addWidget(group)
|
67
|
-
|
68
|
-
def _connect_signals(self):
|
69
|
-
"""Connect internal signals."""
|
70
|
-
self.chk_save_npz.stateChanged.connect(self._handle_save_checkbox_change)
|
71
|
-
self.chk_save_txt.stateChanged.connect(self._handle_save_checkbox_change)
|
72
|
-
|
73
|
-
# Connect all checkboxes to settings changed signal
|
74
|
-
for checkbox in [self.chk_auto_save, self.chk_save_npz, self.chk_save_txt,
|
75
|
-
self.chk_yolo_use_alias, self.chk_save_class_aliases]:
|
76
|
-
checkbox.stateChanged.connect(self.settings_changed)
|
77
|
-
|
78
|
-
def _handle_save_checkbox_change(self):
|
79
|
-
"""Ensure at least one save format is selected."""
|
80
|
-
is_npz_checked = self.chk_save_npz.isChecked()
|
81
|
-
is_txt_checked = self.chk_save_txt.isChecked()
|
82
|
-
|
83
|
-
if not is_npz_checked and not is_txt_checked:
|
84
|
-
sender = self.sender()
|
85
|
-
if sender == self.chk_save_npz:
|
86
|
-
self.chk_save_txt.setChecked(True)
|
87
|
-
else:
|
88
|
-
self.chk_save_npz.setChecked(True)
|
89
|
-
|
90
|
-
def get_settings(self):
|
91
|
-
"""Get current settings as dictionary."""
|
92
|
-
return {
|
93
|
-
'auto_save': self.chk_auto_save.isChecked(),
|
94
|
-
'save_npz': self.chk_save_npz.isChecked(),
|
95
|
-
'save_txt': self.chk_save_txt.isChecked(),
|
96
|
-
'yolo_use_alias': self.chk_yolo_use_alias.isChecked(),
|
97
|
-
'save_class_aliases': self.chk_save_class_aliases.isChecked(),
|
98
|
-
}
|
99
|
-
|
100
|
-
def set_settings(self, settings):
|
101
|
-
"""Set settings from dictionary."""
|
102
|
-
self.chk_auto_save.setChecked(settings.get('auto_save', True))
|
103
|
-
self.chk_save_npz.setChecked(settings.get('save_npz', True))
|
104
|
-
self.chk_save_txt.setChecked(settings.get('save_txt', True))
|
105
|
-
self.chk_yolo_use_alias.setChecked(settings.get('yolo_use_alias', True))
|
1
|
+
"""Settings widget for save options."""
|
2
|
+
|
3
|
+
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QGroupBox
|
4
|
+
from PyQt6.QtCore import pyqtSignal
|
5
|
+
|
6
|
+
|
7
|
+
class SettingsWidget(QWidget):
|
8
|
+
"""Widget for application settings."""
|
9
|
+
|
10
|
+
settings_changed = pyqtSignal()
|
11
|
+
|
12
|
+
def __init__(self, parent=None):
|
13
|
+
super().__init__(parent)
|
14
|
+
self._setup_ui()
|
15
|
+
self._connect_signals()
|
16
|
+
|
17
|
+
def _setup_ui(self):
|
18
|
+
"""Setup the UI layout."""
|
19
|
+
group = QGroupBox("Settings")
|
20
|
+
layout = QVBoxLayout(group)
|
21
|
+
|
22
|
+
# Auto-save
|
23
|
+
self.chk_auto_save = QCheckBox("Auto-Save on Navigate")
|
24
|
+
self.chk_auto_save.setToolTip(
|
25
|
+
"Automatically save work when using arrow keys to change images."
|
26
|
+
)
|
27
|
+
self.chk_auto_save.setChecked(True)
|
28
|
+
layout.addWidget(self.chk_auto_save)
|
29
|
+
|
30
|
+
# Save NPZ
|
31
|
+
self.chk_save_npz = QCheckBox("Save .npz")
|
32
|
+
self.chk_save_npz.setChecked(True)
|
33
|
+
self.chk_save_npz.setToolTip(
|
34
|
+
"Save the final mask as a compressed NumPy NPZ file."
|
35
|
+
)
|
36
|
+
layout.addWidget(self.chk_save_npz)
|
37
|
+
|
38
|
+
# Save TXT
|
39
|
+
self.chk_save_txt = QCheckBox("Save .txt")
|
40
|
+
self.chk_save_txt.setChecked(True)
|
41
|
+
self.chk_save_txt.setToolTip(
|
42
|
+
"Save bounding box annotations in YOLO TXT format."
|
43
|
+
)
|
44
|
+
layout.addWidget(self.chk_save_txt)
|
45
|
+
|
46
|
+
# YOLO with aliases
|
47
|
+
self.chk_yolo_use_alias = QCheckBox("Save YOLO with Class Aliases")
|
48
|
+
self.chk_yolo_use_alias.setToolTip(
|
49
|
+
"If checked, saves YOLO .txt files using class alias names instead of numeric IDs.\n"
|
50
|
+
"This is useful when a separate .yaml or .names file defines the classes."
|
51
|
+
)
|
52
|
+
self.chk_yolo_use_alias.setChecked(True)
|
53
|
+
layout.addWidget(self.chk_yolo_use_alias)
|
54
|
+
|
55
|
+
# Save class aliases
|
56
|
+
self.chk_save_class_aliases = QCheckBox("Save Class Aliases (.json)")
|
57
|
+
self.chk_save_class_aliases.setToolTip(
|
58
|
+
"Save class aliases to a companion JSON file."
|
59
|
+
)
|
60
|
+
self.chk_save_class_aliases.setChecked(False)
|
61
|
+
layout.addWidget(self.chk_save_class_aliases)
|
62
|
+
|
63
|
+
# Main layout
|
64
|
+
main_layout = QVBoxLayout(self)
|
65
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
66
|
+
main_layout.addWidget(group)
|
67
|
+
|
68
|
+
def _connect_signals(self):
|
69
|
+
"""Connect internal signals."""
|
70
|
+
self.chk_save_npz.stateChanged.connect(self._handle_save_checkbox_change)
|
71
|
+
self.chk_save_txt.stateChanged.connect(self._handle_save_checkbox_change)
|
72
|
+
|
73
|
+
# Connect all checkboxes to settings changed signal
|
74
|
+
for checkbox in [self.chk_auto_save, self.chk_save_npz, self.chk_save_txt,
|
75
|
+
self.chk_yolo_use_alias, self.chk_save_class_aliases]:
|
76
|
+
checkbox.stateChanged.connect(self.settings_changed)
|
77
|
+
|
78
|
+
def _handle_save_checkbox_change(self):
|
79
|
+
"""Ensure at least one save format is selected."""
|
80
|
+
is_npz_checked = self.chk_save_npz.isChecked()
|
81
|
+
is_txt_checked = self.chk_save_txt.isChecked()
|
82
|
+
|
83
|
+
if not is_npz_checked and not is_txt_checked:
|
84
|
+
sender = self.sender()
|
85
|
+
if sender == self.chk_save_npz:
|
86
|
+
self.chk_save_txt.setChecked(True)
|
87
|
+
else:
|
88
|
+
self.chk_save_npz.setChecked(True)
|
89
|
+
|
90
|
+
def get_settings(self):
|
91
|
+
"""Get current settings as dictionary."""
|
92
|
+
return {
|
93
|
+
'auto_save': self.chk_auto_save.isChecked(),
|
94
|
+
'save_npz': self.chk_save_npz.isChecked(),
|
95
|
+
'save_txt': self.chk_save_txt.isChecked(),
|
96
|
+
'yolo_use_alias': self.chk_yolo_use_alias.isChecked(),
|
97
|
+
'save_class_aliases': self.chk_save_class_aliases.isChecked(),
|
98
|
+
}
|
99
|
+
|
100
|
+
def set_settings(self, settings):
|
101
|
+
"""Set settings from dictionary."""
|
102
|
+
self.chk_auto_save.setChecked(settings.get('auto_save', True))
|
103
|
+
self.chk_save_npz.setChecked(settings.get('save_npz', True))
|
104
|
+
self.chk_save_txt.setChecked(settings.get('save_txt', True))
|
105
|
+
self.chk_yolo_use_alias.setChecked(settings.get('yolo_use_alias', True))
|
106
106
|
self.chk_save_class_aliases.setChecked(settings.get('save_class_aliases', False))
|