lazylabel-gui 1.1.2__py3-none-any.whl → 1.1.4__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.
Files changed (38) hide show
  1. lazylabel/__init__.py +9 -9
  2. lazylabel/config/__init__.py +7 -7
  3. lazylabel/config/hotkeys.py +96 -57
  4. lazylabel/config/paths.py +40 -41
  5. lazylabel/config/settings.py +65 -66
  6. lazylabel/core/__init__.py +7 -7
  7. lazylabel/core/file_manager.py +122 -106
  8. lazylabel/core/model_manager.py +95 -97
  9. lazylabel/core/segment_manager.py +183 -171
  10. lazylabel/main.py +37 -36
  11. lazylabel/models/__init__.py +5 -5
  12. lazylabel/models/sam_model.py +200 -195
  13. lazylabel/ui/__init__.py +8 -8
  14. lazylabel/ui/control_panel.py +245 -241
  15. lazylabel/ui/editable_vertex.py +64 -64
  16. lazylabel/ui/hotkey_dialog.py +416 -384
  17. lazylabel/ui/hoverable_pixelmap_item.py +22 -22
  18. lazylabel/ui/hoverable_polygon_item.py +38 -39
  19. lazylabel/ui/main_window.py +1860 -1659
  20. lazylabel/ui/numeric_table_widget_item.py +9 -9
  21. lazylabel/ui/photo_viewer.py +51 -54
  22. lazylabel/ui/reorderable_class_table.py +60 -61
  23. lazylabel/ui/right_panel.py +332 -315
  24. lazylabel/ui/widgets/__init__.py +8 -8
  25. lazylabel/ui/widgets/adjustments_widget.py +108 -108
  26. lazylabel/ui/widgets/model_selection_widget.py +101 -94
  27. lazylabel/ui/widgets/settings_widget.py +113 -106
  28. lazylabel/ui/widgets/status_bar.py +109 -109
  29. lazylabel/utils/__init__.py +6 -6
  30. lazylabel/utils/custom_file_system_model.py +133 -132
  31. lazylabel/utils/utils.py +12 -12
  32. {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/METADATA +243 -197
  33. lazylabel_gui-1.1.4.dist-info/RECORD +37 -0
  34. {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/licenses/LICENSE +21 -21
  35. lazylabel_gui-1.1.2.dist-info/RECORD +0 -37
  36. {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/WHEEL +0 -0
  37. {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/entry_points.txt +0 -0
  38. {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/top_level.txt +0 -0
lazylabel/__init__.py CHANGED
@@ -1,9 +1,9 @@
1
- """LazyLabel - AI-assisted image segmentation tool."""
2
-
3
- __version__ = "1.0.9"
4
- __author__ = "Deniz N. Cakan"
5
- __email__ = "deniz.n.cakan@gmail.com"
6
-
7
- from .main import main
8
-
9
- __all__ = ['main']
1
+ """LazyLabel - AI-assisted image segmentation tool."""
2
+
3
+ __version__ = "1.0.9"
4
+ __author__ = "Deniz N. Cakan"
5
+ __email__ = "deniz.n.cakan@gmail.com"
6
+
7
+ from .main import main
8
+
9
+ __all__ = ["main"]
@@ -1,7 +1,7 @@
1
- """Configuration management for LazyLabel."""
2
-
3
- from .settings import Settings, DEFAULT_SETTINGS
4
- from .paths import Paths
5
- from .hotkeys import HotkeyManager, HotkeyAction
6
-
7
- __all__ = ['Settings', 'DEFAULT_SETTINGS', 'Paths', 'HotkeyManager', 'HotkeyAction']
1
+ """Configuration management for LazyLabel."""
2
+
3
+ from .hotkeys import HotkeyAction, HotkeyManager
4
+ from .paths import Paths
5
+ from .settings import DEFAULT_SETTINGS, Settings
6
+
7
+ __all__ = ["Settings", "DEFAULT_SETTINGS", "Paths", "HotkeyManager", "HotkeyAction"]
@@ -2,89 +2,128 @@
2
2
 
3
3
  import json
4
4
  import os
5
- from dataclasses import dataclass, asdict
6
- from typing import Dict, Optional, List, Tuple
7
- from PyQt6.QtCore import Qt
5
+ from dataclasses import dataclass
6
+
8
7
  from PyQt6.QtGui import QKeySequence
9
8
 
10
9
 
11
10
  @dataclass
12
11
  class HotkeyAction:
13
12
  """Represents a hotkey action with primary and secondary keys."""
13
+
14
14
  name: str
15
15
  description: str
16
16
  primary_key: str
17
- secondary_key: Optional[str] = None
17
+ secondary_key: str | None = None
18
18
  category: str = "General"
19
19
  mouse_related: bool = False # Cannot be reassigned if True
20
20
 
21
21
 
22
22
  class HotkeyManager:
23
23
  """Manages application hotkeys with persistence."""
24
-
24
+
25
25
  def __init__(self, config_dir: str):
26
26
  self.config_dir = config_dir
27
27
  self.hotkeys_file = os.path.join(config_dir, "hotkeys.json")
28
- self.actions: Dict[str, HotkeyAction] = {}
28
+ self.actions: dict[str, HotkeyAction] = {}
29
29
  self._initialize_default_hotkeys()
30
30
  self.load_hotkeys()
31
-
31
+
32
32
  def _initialize_default_hotkeys(self):
33
33
  """Initialize default hotkey mappings."""
34
34
  default_hotkeys = [
35
35
  # Navigation
36
- HotkeyAction("load_next_image", "Load Next Image", "Right", category="Navigation"),
37
- HotkeyAction("load_previous_image", "Load Previous Image", "Left", category="Navigation"),
36
+ HotkeyAction(
37
+ "load_next_image", "Load Next Image", "Right", category="Navigation"
38
+ ),
39
+ HotkeyAction(
40
+ "load_previous_image",
41
+ "Load Previous Image",
42
+ "Left",
43
+ category="Navigation",
44
+ ),
38
45
  HotkeyAction("fit_view", "Fit View", ".", category="Navigation"),
39
-
40
46
  # Modes
41
47
  HotkeyAction("sam_mode", "Point Mode (SAM)", "1", category="Modes"),
42
48
  HotkeyAction("polygon_mode", "Polygon Mode", "2", category="Modes"),
49
+ HotkeyAction("bbox_mode", "Bounding Box Mode", "3", category="Modes"),
43
50
  HotkeyAction("selection_mode", "Selection Mode", "E", category="Modes"),
44
51
  HotkeyAction("pan_mode", "Pan Mode", "Q", category="Modes"),
45
52
  HotkeyAction("edit_mode", "Edit Mode", "R", category="Modes"),
46
-
47
53
  # Actions
48
- HotkeyAction("clear_points", "Clear Points/Vertices", "C", category="Actions"),
49
- HotkeyAction("save_segment", "Save Current Segment", "Space", category="Actions"),
54
+ HotkeyAction(
55
+ "clear_points", "Clear Points/Vertices", "C", category="Actions"
56
+ ),
57
+ HotkeyAction(
58
+ "save_segment", "Save Current Segment", "Space", category="Actions"
59
+ ),
50
60
  HotkeyAction("save_output", "Save Output", "Return", category="Actions"),
51
- HotkeyAction("save_output_alt", "Save Output (Alt)", "Enter", category="Actions"),
61
+ HotkeyAction(
62
+ "save_output_alt", "Save Output (Alt)", "Enter", category="Actions"
63
+ ),
52
64
  HotkeyAction("undo", "Undo Last Action", "Ctrl+Z", category="Actions"),
53
- HotkeyAction("escape", "Cancel/Clear Selection", "Escape", category="Actions"),
54
-
65
+ HotkeyAction(
66
+ "redo", "Redo Last Action", "Ctrl+Y", "Ctrl+Shift+Z", category="Actions"
67
+ ),
68
+ HotkeyAction(
69
+ "escape", "Cancel/Clear Selection", "Escape", category="Actions"
70
+ ),
55
71
  # Segments
56
- HotkeyAction("merge_segments", "Merge Selected Segments", "M", category="Segments"),
57
- HotkeyAction("delete_segments", "Delete Selected Segments", "V", category="Segments"),
58
- HotkeyAction("delete_segments_alt", "Delete Selected Segments (Alt)", "Backspace", category="Segments"),
59
- HotkeyAction("select_all", "Select All Segments", "Ctrl+A", category="Segments"),
60
-
72
+ HotkeyAction(
73
+ "merge_segments", "Merge Selected Segments", "M", category="Segments"
74
+ ),
75
+ HotkeyAction(
76
+ "delete_segments", "Delete Selected Segments", "V", category="Segments"
77
+ ),
78
+ HotkeyAction(
79
+ "delete_segments_alt",
80
+ "Delete Selected Segments (Alt)",
81
+ "Backspace",
82
+ category="Segments",
83
+ ),
84
+ HotkeyAction(
85
+ "select_all", "Select All Segments", "Ctrl+A", category="Segments"
86
+ ),
61
87
  # View
62
88
  HotkeyAction("zoom_in", "Zoom In", "Ctrl+Plus", category="View"),
63
89
  HotkeyAction("zoom_out", "Zoom Out", "Ctrl+Minus", category="View"),
64
-
65
90
  # Movement (WASD)
66
91
  HotkeyAction("pan_up", "Pan Up", "W", category="Movement"),
67
92
  HotkeyAction("pan_down", "Pan Down", "S", category="Movement"),
68
93
  HotkeyAction("pan_left", "Pan Left", "A", category="Movement"),
69
94
  HotkeyAction("pan_right", "Pan Right", "D", category="Movement"),
70
-
71
95
  # Mouse-related (cannot be reassigned)
72
- HotkeyAction("left_click", "Add Positive Point / Select", "Left Click",
73
- category="Mouse", mouse_related=True),
74
- HotkeyAction("right_click", "Add Negative Point", "Right Click",
75
- category="Mouse", mouse_related=True),
76
- HotkeyAction("mouse_drag", "Drag/Pan", "Mouse Drag",
77
- category="Mouse", mouse_related=True),
96
+ HotkeyAction(
97
+ "left_click",
98
+ "Add Positive Point / Select",
99
+ "Left Click",
100
+ category="Mouse",
101
+ mouse_related=True,
102
+ ),
103
+ HotkeyAction(
104
+ "right_click",
105
+ "Add Negative Point",
106
+ "Right Click",
107
+ category="Mouse",
108
+ mouse_related=True,
109
+ ),
110
+ HotkeyAction(
111
+ "mouse_drag",
112
+ "Drag/Pan",
113
+ "Mouse Drag",
114
+ category="Mouse",
115
+ mouse_related=True,
116
+ ),
78
117
  ]
79
-
118
+
80
119
  for action in default_hotkeys:
81
120
  self.actions[action.name] = action
82
-
83
- def get_action(self, action_name: str) -> Optional[HotkeyAction]:
121
+
122
+ def get_action(self, action_name: str) -> HotkeyAction | None:
84
123
  """Get hotkey action by name."""
85
124
  return self.actions.get(action_name)
86
-
87
- def get_actions_by_category(self) -> Dict[str, List[HotkeyAction]]:
125
+
126
+ def get_actions_by_category(self) -> dict[str, list[HotkeyAction]]:
88
127
  """Get actions grouped by category."""
89
128
  categories = {}
90
129
  for action in self.actions.values():
@@ -92,29 +131,29 @@ class HotkeyManager:
92
131
  categories[action.category] = []
93
132
  categories[action.category].append(action)
94
133
  return categories
95
-
134
+
96
135
  def set_primary_key(self, action_name: str, key: str) -> bool:
97
136
  """Set primary key for an action."""
98
137
  if action_name in self.actions and not self.actions[action_name].mouse_related:
99
138
  self.actions[action_name].primary_key = key
100
139
  return True
101
140
  return False
102
-
103
- def set_secondary_key(self, action_name: str, key: Optional[str]) -> bool:
141
+
142
+ def set_secondary_key(self, action_name: str, key: str | None) -> bool:
104
143
  """Set secondary key for an action."""
105
144
  if action_name in self.actions and not self.actions[action_name].mouse_related:
106
145
  self.actions[action_name].secondary_key = key
107
146
  return True
108
147
  return False
109
-
110
- def get_key_for_action(self, action_name: str) -> Tuple[Optional[str], Optional[str]]:
148
+
149
+ def get_key_for_action(self, action_name: str) -> tuple[str | None, str | None]:
111
150
  """Get primary and secondary keys for an action."""
112
151
  action = self.actions.get(action_name)
113
152
  if action:
114
153
  return action.primary_key, action.secondary_key
115
154
  return None, None
116
-
117
- def is_key_in_use(self, key: str, exclude_action: str = None) -> Optional[str]:
155
+
156
+ def is_key_in_use(self, key: str, exclude_action: str = None) -> str | None:
118
157
  """Check if a key is already in use by another action."""
119
158
  for name, action in self.actions.items():
120
159
  if name == exclude_action:
@@ -122,48 +161,48 @@ class HotkeyManager:
122
161
  if action.primary_key == key or action.secondary_key == key:
123
162
  return name
124
163
  return None
125
-
164
+
126
165
  def reset_to_defaults(self):
127
166
  """Reset all hotkeys to default values."""
128
167
  self._initialize_default_hotkeys()
129
-
168
+
130
169
  def save_hotkeys(self):
131
170
  """Save hotkeys to file."""
132
171
  os.makedirs(self.config_dir, exist_ok=True)
133
-
172
+
134
173
  # Convert to serializable format
135
174
  data = {}
136
175
  for name, action in self.actions.items():
137
176
  if not action.mouse_related: # Don't save mouse-related actions
138
177
  data[name] = {
139
- 'primary_key': action.primary_key,
140
- 'secondary_key': action.secondary_key
178
+ "primary_key": action.primary_key,
179
+ "secondary_key": action.secondary_key,
141
180
  }
142
-
143
- with open(self.hotkeys_file, 'w') as f:
181
+
182
+ with open(self.hotkeys_file, "w") as f:
144
183
  json.dump(data, f, indent=4)
145
-
184
+
146
185
  def load_hotkeys(self):
147
186
  """Load hotkeys from file."""
148
187
  if not os.path.exists(self.hotkeys_file):
149
188
  return
150
-
189
+
151
190
  try:
152
- with open(self.hotkeys_file, 'r') as f:
191
+ with open(self.hotkeys_file) as f:
153
192
  data = json.load(f)
154
-
193
+
155
194
  for name, keys in data.items():
156
195
  if name in self.actions and not self.actions[name].mouse_related:
157
- self.actions[name].primary_key = keys.get('primary_key', '')
158
- self.actions[name].secondary_key = keys.get('secondary_key')
196
+ self.actions[name].primary_key = keys.get("primary_key", "")
197
+ self.actions[name].secondary_key = keys.get("secondary_key")
159
198
  except (json.JSONDecodeError, KeyError, FileNotFoundError):
160
199
  # If loading fails, keep defaults
161
200
  pass
162
-
201
+
163
202
  def key_sequence_to_string(self, key_sequence: QKeySequence) -> str:
164
203
  """Convert QKeySequence to string representation."""
165
204
  return key_sequence.toString()
166
-
205
+
167
206
  def string_to_key_sequence(self, key_string: str) -> QKeySequence:
168
207
  """Convert string to QKeySequence."""
169
- return QKeySequence(key_string)
208
+ return QKeySequence(key_string)
lazylabel/config/paths.py CHANGED
@@ -1,41 +1,40 @@
1
- """Path management for LazyLabel."""
2
-
3
- import os
4
- from pathlib import Path
5
-
6
-
7
- class Paths:
8
- """Centralized path management."""
9
-
10
- def __init__(self):
11
- self.app_dir = Path(__file__).parent.parent
12
- self.models_dir = self.app_dir / "models"
13
- self.config_dir = Path.home() / ".config" / "lazylabel"
14
- self.cache_dir = Path.home() / ".cache" / "lazylabel"
15
-
16
- # Ensure directories exist
17
- self.models_dir.mkdir(exist_ok=True)
18
- self.config_dir.mkdir(parents=True, exist_ok=True)
19
-
20
- @property
21
- def settings_file(self) -> Path:
22
- """Path to settings file."""
23
- return self.config_dir / "settings.json"
24
-
25
- @property
26
- def demo_pictures_dir(self) -> Path:
27
- """Path to demo pictures directory."""
28
- return self.app_dir / "demo_pictures"
29
-
30
- @property
31
- def logo_path(self) -> Path:
32
- """Path to application logo."""
33
- return self.demo_pictures_dir / "logo2.png"
34
-
35
- def get_model_path(self, filename: str) -> Path:
36
- """Get path for a model file."""
37
- return self.models_dir / filename
38
-
39
- def get_old_cache_model_path(self, filename: str) -> Path:
40
- """Get path for model in old cache location."""
41
- return self.cache_dir / filename
1
+ """Path management for LazyLabel."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ class Paths:
7
+ """Centralized path management."""
8
+
9
+ def __init__(self):
10
+ self.app_dir = Path(__file__).parent.parent
11
+ self.models_dir = self.app_dir / "models"
12
+ self.config_dir = Path.home() / ".config" / "lazylabel"
13
+ self.cache_dir = Path.home() / ".cache" / "lazylabel"
14
+
15
+ # Ensure directories exist
16
+ self.models_dir.mkdir(exist_ok=True)
17
+ self.config_dir.mkdir(parents=True, exist_ok=True)
18
+
19
+ @property
20
+ def settings_file(self) -> Path:
21
+ """Path to settings file."""
22
+ return self.config_dir / "settings.json"
23
+
24
+ @property
25
+ def demo_pictures_dir(self) -> Path:
26
+ """Path to demo pictures directory."""
27
+ return self.app_dir / "demo_pictures"
28
+
29
+ @property
30
+ def logo_path(self) -> Path:
31
+ """Path to application logo."""
32
+ return self.demo_pictures_dir / "logo2.png"
33
+
34
+ def get_model_path(self, filename: str) -> Path:
35
+ """Get path for a model file."""
36
+ return self.models_dir / filename
37
+
38
+ def get_old_cache_model_path(self, filename: str) -> Path:
39
+ """Get path for model in old cache location."""
40
+ return self.cache_dir / filename
@@ -1,66 +1,65 @@
1
- """Application settings and configuration."""
2
-
3
- import os
4
- from dataclasses import dataclass, asdict
5
- from typing import Dict, Any
6
- import json
7
-
8
-
9
- @dataclass
10
- class Settings:
11
- """Application settings with defaults."""
12
-
13
- # UI Settings
14
- window_width: int = 1600
15
- window_height: int = 900
16
- left_panel_width: int = 250
17
- right_panel_width: int = 350
18
-
19
- # Annotation Settings
20
- point_radius: float = 0.3
21
- line_thickness: float = 0.5
22
- pan_multiplier: float = 1.0
23
- polygon_join_threshold: int = 2
24
-
25
- # Model Settings
26
- default_model_type: str = "vit_h"
27
- default_model_filename: str = "sam_vit_h_4b8939.pth"
28
-
29
- # Save Settings
30
- auto_save: bool = True
31
- save_npz: bool = True
32
- save_txt: bool = True
33
- save_class_aliases: bool = False
34
- yolo_use_alias: bool = True
35
-
36
- # UI State
37
- annotation_size_multiplier: float = 1.0
38
-
39
- def save_to_file(self, filepath: str) -> None:
40
- """Save settings to JSON file."""
41
- os.makedirs(os.path.dirname(filepath), exist_ok=True)
42
- with open(filepath, 'w') as f:
43
- json.dump(asdict(self), f, indent=4)
44
-
45
- @classmethod
46
- def load_from_file(cls, filepath: str) -> 'Settings':
47
- """Load settings from JSON file."""
48
- if not os.path.exists(filepath):
49
- return cls()
50
-
51
- try:
52
- with open(filepath, 'r') as f:
53
- data = json.load(f)
54
- return cls(**data)
55
- except (json.JSONDecodeError, TypeError):
56
- return cls()
57
-
58
- def update(self, **kwargs) -> None:
59
- """Update settings with new values."""
60
- for key, value in kwargs.items():
61
- if hasattr(self, key):
62
- setattr(self, key, value)
63
-
64
-
65
- # Default settings instance
66
- DEFAULT_SETTINGS = Settings()
1
+ """Application settings and configuration."""
2
+
3
+ import json
4
+ import os
5
+ from dataclasses import asdict, dataclass
6
+
7
+
8
+ @dataclass
9
+ class Settings:
10
+ """Application settings with defaults."""
11
+
12
+ # UI Settings
13
+ window_width: int = 1600
14
+ window_height: int = 900
15
+ left_panel_width: int = 250
16
+ right_panel_width: int = 350
17
+
18
+ # Annotation Settings
19
+ point_radius: float = 0.3
20
+ line_thickness: float = 0.5
21
+ pan_multiplier: float = 1.0
22
+ polygon_join_threshold: int = 2
23
+
24
+ # Model Settings
25
+ default_model_type: str = "vit_h"
26
+ default_model_filename: str = "sam_vit_h_4b8939.pth"
27
+
28
+ # Save Settings
29
+ auto_save: bool = True
30
+ save_npz: bool = True
31
+ save_txt: bool = True
32
+ save_class_aliases: bool = False
33
+ yolo_use_alias: bool = True
34
+
35
+ # UI State
36
+ annotation_size_multiplier: float = 1.0
37
+
38
+ def save_to_file(self, filepath: str) -> None:
39
+ """Save settings to JSON file."""
40
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
41
+ with open(filepath, "w") as f:
42
+ json.dump(asdict(self), f, indent=4)
43
+
44
+ @classmethod
45
+ def load_from_file(cls, filepath: str) -> "Settings":
46
+ """Load settings from JSON file."""
47
+ if not os.path.exists(filepath):
48
+ return cls()
49
+
50
+ try:
51
+ with open(filepath) as f:
52
+ data = json.load(f)
53
+ return cls(**data)
54
+ except (json.JSONDecodeError, TypeError):
55
+ return cls()
56
+
57
+ def update(self, **kwargs) -> None:
58
+ """Update settings with new values."""
59
+ for key, value in kwargs.items():
60
+ if hasattr(self, key):
61
+ setattr(self, key, value)
62
+
63
+
64
+ # Default settings instance
65
+ DEFAULT_SETTINGS = Settings()
@@ -1,7 +1,7 @@
1
- """Core business logic for LazyLabel."""
2
-
3
- from .segment_manager import SegmentManager
4
- from .model_manager import ModelManager
5
- from .file_manager import FileManager
6
-
7
- __all__ = ['SegmentManager', 'ModelManager', 'FileManager']
1
+ """Core business logic for LazyLabel."""
2
+
3
+ from .file_manager import FileManager
4
+ from .model_manager import ModelManager
5
+ from .segment_manager import SegmentManager
6
+
7
+ __all__ = ["SegmentManager", "ModelManager", "FileManager"]