lazylabel-gui 1.0.8__tar.gz → 1.1.0__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.
Files changed (47) hide show
  1. {lazylabel_gui-1.0.8 → lazylabel_gui-1.1.0}/PKG-INFO +62 -12
  2. lazylabel_gui-1.1.0/README.md +148 -0
  3. {lazylabel_gui-1.0.8 → lazylabel_gui-1.1.0}/pyproject.toml +2 -2
  4. lazylabel_gui-1.1.0/src/lazylabel/__init__.py +9 -0
  5. lazylabel_gui-1.1.0/src/lazylabel/config/__init__.py +7 -0
  6. lazylabel_gui-1.1.0/src/lazylabel/config/hotkeys.py +169 -0
  7. lazylabel_gui-1.1.0/src/lazylabel/config/paths.py +41 -0
  8. lazylabel_gui-1.1.0/src/lazylabel/config/settings.py +66 -0
  9. lazylabel_gui-1.1.0/src/lazylabel/core/__init__.py +7 -0
  10. lazylabel_gui-1.1.0/src/lazylabel/core/file_manager.py +106 -0
  11. lazylabel_gui-1.1.0/src/lazylabel/core/model_manager.py +94 -0
  12. lazylabel_gui-1.1.0/src/lazylabel/core/segment_manager.py +140 -0
  13. lazylabel_gui-1.1.0/src/lazylabel/main.py +22 -0
  14. lazylabel_gui-1.1.0/src/lazylabel/models/__init__.py +5 -0
  15. lazylabel_gui-1.1.0/src/lazylabel/models/sam_model.py +154 -0
  16. lazylabel_gui-1.1.0/src/lazylabel/ui/__init__.py +8 -0
  17. lazylabel_gui-1.1.0/src/lazylabel/ui/control_panel.py +220 -0
  18. {lazylabel_gui-1.0.8/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/editable_vertex.py +25 -3
  19. lazylabel_gui-1.1.0/src/lazylabel/ui/hotkey_dialog.py +384 -0
  20. {lazylabel_gui-1.0.8/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/hoverable_polygon_item.py +17 -1
  21. lazylabel_gui-1.1.0/src/lazylabel/ui/main_window.py +1264 -0
  22. lazylabel_gui-1.1.0/src/lazylabel/ui/right_panel.py +239 -0
  23. lazylabel_gui-1.1.0/src/lazylabel/ui/widgets/__init__.py +7 -0
  24. lazylabel_gui-1.1.0/src/lazylabel/ui/widgets/adjustments_widget.py +107 -0
  25. lazylabel_gui-1.1.0/src/lazylabel/ui/widgets/model_selection_widget.py +94 -0
  26. lazylabel_gui-1.1.0/src/lazylabel/ui/widgets/settings_widget.py +106 -0
  27. lazylabel_gui-1.1.0/src/lazylabel/utils/__init__.py +6 -0
  28. lazylabel_gui-1.1.0/src/lazylabel/utils/custom_file_system_model.py +132 -0
  29. {lazylabel_gui-1.0.8 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/PKG-INFO +62 -12
  30. lazylabel_gui-1.1.0/src/lazylabel_gui.egg-info/SOURCES.txt +39 -0
  31. lazylabel_gui-1.0.8/README.md +0 -98
  32. lazylabel_gui-1.0.8/src/lazylabel/controls.py +0 -261
  33. lazylabel_gui-1.0.8/src/lazylabel/custom_file_system_model.py +0 -72
  34. lazylabel_gui-1.0.8/src/lazylabel/main.py +0 -1258
  35. lazylabel_gui-1.0.8/src/lazylabel/sam_model.py +0 -70
  36. lazylabel_gui-1.0.8/src/lazylabel_gui.egg-info/SOURCES.txt +0 -20
  37. {lazylabel_gui-1.0.8 → lazylabel_gui-1.1.0}/LICENSE +0 -0
  38. {lazylabel_gui-1.0.8 → lazylabel_gui-1.1.0}/setup.cfg +0 -0
  39. {lazylabel_gui-1.0.8/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/hoverable_pixelmap_item.py +0 -0
  40. {lazylabel_gui-1.0.8/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/numeric_table_widget_item.py +0 -0
  41. {lazylabel_gui-1.0.8/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/photo_viewer.py +0 -0
  42. {lazylabel_gui-1.0.8/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/reorderable_class_table.py +0 -0
  43. {lazylabel_gui-1.0.8/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/utils}/utils.py +0 -0
  44. {lazylabel_gui-1.0.8 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/dependency_links.txt +0 -0
  45. {lazylabel_gui-1.0.8 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/entry_points.txt +0 -0
  46. {lazylabel_gui-1.0.8 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/requires.txt +0 -0
  47. {lazylabel_gui-1.0.8 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lazylabel-gui
3
- Version: 1.0.8
4
- Summary: An image segmentation GUI for generating mask tensors.
3
+ Version: 1.1.0
4
+ Summary: An image segmentation GUI for generating ML ready mask tensors and annotations.
5
5
  Author-email: "Deniz N. Cakan" <deniz.n.cakan@gmail.com>
6
6
  License: MIT License
7
7
 
@@ -48,7 +48,8 @@ Requires-Dist: tqdm>=4.67.1
48
48
  Dynamic: license-file
49
49
 
50
50
  # <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo2.png" alt="LazyLabel Logo" style="height:60px; vertical-align:middle;" /> <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo_black.png" alt="LazyLabel Cursive" style="height:60px; vertical-align:middle;" />
51
- LazyLabel is an intuitive, AI-assisted image segmentation tool. It uses Meta's Segment Anything Model (SAM) for quick, precise mask generation, alongside advanced polygon editing for fine-tuned control. Outputs are saved in a clean, one-hot encoded `.npz` format for easy machine learning integration and in YOLO `.txt` format.
51
+
52
+ LazyLabel is an intuitive, AI-assisted image segmentation tool built with a modern, modular architecture. It uses Meta's Segment Anything Model (SAM) for quick, precise mask generation, alongside advanced polygon editing for fine-tuned control. Features comprehensive model management, customizable hotkeys, and outputs in clean, one-hot encoded `.npz` format for easy machine learning integration.
52
53
 
53
54
  Inspired by [LabelMe](https://github.com/wkentaro/labelme?tab=readme-ov-file#installation) and [Segment-Anything-UI](https://github.com/branislavhesko/segment-anything-ui/tree/main).
54
55
 
@@ -58,19 +59,33 @@ Inspired by [LabelMe](https://github.com/wkentaro/labelme?tab=readme-ov-file#ins
58
59
 
59
60
  ## ✨ Core Features
60
61
 
61
- * **AI-Powered Segmentation**: Generate masks with simple left-click (positive) and right-click (negative) interactions.
62
- * **Vector Polygon Tool**: Full control to draw, edit, and reshape polygons. Drag vertices or move entire shapes.
63
- * **Advanced Class Management**: Assign multiple segments to a single class ID for organized labeling.
64
- * **Intuitive Editing & Refinement**: Select, merge, and re-order segments.
65
- * **Interactive UI**: Color-coded segments, sortable lists, and hover highlighting.
66
- * **Smart I/O**: Loads existing `.npz` masks; saves work as clean, one-hot encoded outputs.
62
+ ### **AI-Powered Segmentation**
63
+ * Generate masks with simple left-click (positive) and right-click (negative) interactions
64
+ * Multiple SAM model support with easy switching
65
+ * Custom model loading from any directory
66
+
67
+ ### **Advanced Editing Tools**
68
+ * **Vector Polygon Tool**: Full control to draw, edit, and reshape polygons
69
+ * **Vertex Editing**: Drag vertices or move entire shapes with precision
70
+ * **Selection & Merging**: Select, merge, and re-order segments intuitively
71
+
72
+ ### **Professional Workflow**
73
+ * **Customizable Hotkeys**: Personalize keyboard shortcuts for all functions
74
+ * **Advanced Class Management**: Assign multiple segments to single class IDs
75
+ * **Smart I/O**: Load existing `.npz` masks; save as clean, one-hot encoded outputs
76
+ * **Interactive UI**: Color-coded segments, sortable lists, and hover highlighting
77
+
78
+ ### **Modern Architecture**
79
+ * **Modular Design**: Clean, maintainable codebase with separated concerns
80
+ * **Model Management**: Dedicated model storage and switching system
81
+ * **Persistent Settings**: User preferences saved between sessions
67
82
 
68
83
  ---
69
84
 
70
85
  ## 🚀 Getting Started
71
86
 
72
87
  ### Prerequisites
73
- **Python 3.10**
88
+ **Python 3.10+**
74
89
 
75
90
  ### Installation
76
91
 
@@ -90,7 +105,7 @@ Inspired by [LabelMe](https://github.com/wkentaro/labelme?tab=readme-ov-file#ins
90
105
  git clone https://github.com/dnzckn/LazyLabel.git
91
106
  cd LazyLabel
92
107
  ```
93
- 2. Install in editable mode, which links the installed package to your source directory:
108
+ 2. Install in editable mode:
94
109
  ```bash
95
110
  pip install -e .
96
111
  ```
@@ -99,12 +114,20 @@ Inspired by [LabelMe](https://github.com/wkentaro/labelme?tab=readme-ov-file#ins
99
114
  lazylabel-gui
100
115
  ```
101
116
 
102
- **Note**: On the first run, the application will automatically download the SAM model checkpoint (~2.5 GB) from Meta's repository to a local cache. This is a one-time download.
117
+ ### Model Management
118
+ * **Default Storage**: Models are stored in `src/lazylabel/models/` directory
119
+ * **Custom Models**: Click "Browse Models" to select custom model folders
120
+ * **Model Switching**: Use the dropdown to switch between available models
121
+ * **Auto-Detection**: Application automatically detects all `.pth` files in selected directories
122
+
123
+ **Note**: On the first run, the application will automatically download the SAM model checkpoint (~2.5 GB) from Meta's repository to the models directory. This is a one-time download.
103
124
 
104
125
  ---
105
126
 
106
127
  ## ⌨️ Controls & Keybinds
107
128
 
129
+ > **💡 Tip**: All hotkeys are fully customizable! Click the "Hotkeys" button in the control panel to personalize your shortcuts.
130
+
108
131
  ### Modes
109
132
  | Key | Action |
110
133
  |---|---|
@@ -143,5 +166,32 @@ Each channel is a binary mask for a class, combining all assigned segments into
143
166
 
144
167
  ---
145
168
 
169
+ ## 🏗️ Architecture
170
+
171
+ LazyLabel features a modern, modular architecture designed for maintainability and extensibility:
172
+
173
+ * **Modular Design**: Clean separation between UI, business logic, and configuration
174
+ * **Signal-Based Communication**: Loose coupling between components using PyQt signals
175
+ * **Persistent Configuration**: User settings and preferences saved between sessions
176
+ * **Extensible Model System**: Easy integration of new SAM models and types
177
+
178
+ For detailed technical documentation, see [ARCHITECTURE.md](src/lazylabel/ARCHITECTURE.md).
179
+
180
+ ---
181
+
182
+ ## ⌨️ Hotkey Customization
183
+
184
+ LazyLabel includes a comprehensive hotkey management system:
185
+
186
+ * **Full Customization**: Personalize keyboard shortcuts for all 27+ functions
187
+ * **Category Organization**: Hotkeys organized by function (Modes, Actions, Navigation, etc.)
188
+ * **Primary & Secondary Keys**: Set multiple shortcuts for the same action
189
+ * **Persistent Settings**: Custom hotkeys saved between sessions
190
+ * **Conflict Prevention**: System prevents duplicate key assignments
191
+
192
+ For complete hotkey documentation, see [HOTKEY_FEATURE.md](src/lazylabel/HOTKEY_FEATURE.md).
193
+
194
+ ---
195
+
146
196
  ## ☕ Support LazyLabel
147
197
  [If you found LazyLabel helpful, consider supporting the project!](https://buymeacoffee.com/dnzckn)
@@ -0,0 +1,148 @@
1
+ # <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo2.png" alt="LazyLabel Logo" style="height:60px; vertical-align:middle;" /> <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo_black.png" alt="LazyLabel Cursive" style="height:60px; vertical-align:middle;" />
2
+
3
+ LazyLabel is an intuitive, AI-assisted image segmentation tool built with a modern, modular architecture. It uses Meta's Segment Anything Model (SAM) for quick, precise mask generation, alongside advanced polygon editing for fine-tuned control. Features comprehensive model management, customizable hotkeys, and outputs in clean, one-hot encoded `.npz` format for easy machine learning integration.
4
+
5
+ Inspired by [LabelMe](https://github.com/wkentaro/labelme?tab=readme-ov-file#installation) and [Segment-Anything-UI](https://github.com/branislavhesko/segment-anything-ui/tree/main).
6
+
7
+ ![LazyLabel Screenshot](https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/gui.PNG)
8
+
9
+ ---
10
+
11
+ ## ✨ Core Features
12
+
13
+ ### **AI-Powered Segmentation**
14
+ * Generate masks with simple left-click (positive) and right-click (negative) interactions
15
+ * Multiple SAM model support with easy switching
16
+ * Custom model loading from any directory
17
+
18
+ ### **Advanced Editing Tools**
19
+ * **Vector Polygon Tool**: Full control to draw, edit, and reshape polygons
20
+ * **Vertex Editing**: Drag vertices or move entire shapes with precision
21
+ * **Selection & Merging**: Select, merge, and re-order segments intuitively
22
+
23
+ ### **Professional Workflow**
24
+ * **Customizable Hotkeys**: Personalize keyboard shortcuts for all functions
25
+ * **Advanced Class Management**: Assign multiple segments to single class IDs
26
+ * **Smart I/O**: Load existing `.npz` masks; save as clean, one-hot encoded outputs
27
+ * **Interactive UI**: Color-coded segments, sortable lists, and hover highlighting
28
+
29
+ ### **Modern Architecture**
30
+ * **Modular Design**: Clean, maintainable codebase with separated concerns
31
+ * **Model Management**: Dedicated model storage and switching system
32
+ * **Persistent Settings**: User preferences saved between sessions
33
+
34
+ ---
35
+
36
+ ## 🚀 Getting Started
37
+
38
+ ### Prerequisites
39
+ **Python 3.10+**
40
+
41
+ ### Installation
42
+
43
+ #### For Users [via PyPI](https://pypi.org/project/lazylabel-gui/)
44
+ 1. Install LazyLabel directly:
45
+ ```bash
46
+ pip install lazylabel-gui
47
+ ```
48
+ 2. Run the application:
49
+ ```bash
50
+ lazylabel-gui
51
+ ```
52
+
53
+ #### For Developers (from Source)
54
+ 1. Clone the repository:
55
+ ```bash
56
+ git clone https://github.com/dnzckn/LazyLabel.git
57
+ cd LazyLabel
58
+ ```
59
+ 2. Install in editable mode:
60
+ ```bash
61
+ pip install -e .
62
+ ```
63
+ 3. Run the application:
64
+ ```bash
65
+ lazylabel-gui
66
+ ```
67
+
68
+ ### Model Management
69
+ * **Default Storage**: Models are stored in `src/lazylabel/models/` directory
70
+ * **Custom Models**: Click "Browse Models" to select custom model folders
71
+ * **Model Switching**: Use the dropdown to switch between available models
72
+ * **Auto-Detection**: Application automatically detects all `.pth` files in selected directories
73
+
74
+ **Note**: On the first run, the application will automatically download the SAM model checkpoint (~2.5 GB) from Meta's repository to the models directory. This is a one-time download.
75
+
76
+ ---
77
+
78
+ ## ⌨️ Controls & Keybinds
79
+
80
+ > **💡 Tip**: All hotkeys are fully customizable! Click the "Hotkeys" button in the control panel to personalize your shortcuts.
81
+
82
+ ### Modes
83
+ | Key | Action |
84
+ |---|---|
85
+ | `1` | Enter **Point Mode** (for AI segmentation). |
86
+ | `2` | Enter **Polygon Drawing Mode**. |
87
+ | `E` | Toggle **Selection Mode** to select existing segments. |
88
+ | `R` | Enter **Edit Mode** for selected polygons (drag shape or vertices). |
89
+ | `Q` | Toggle **Pan Mode** (click and drag the image). |
90
+
91
+ ### Actions
92
+ | Key(s) | Action |
93
+ |---|---|
94
+ | `L-Click` | Add positive point (Point Mode) or polygon vertex. |
95
+ | `R-Click` | Add negative point (Point Mode). |
96
+ | `Ctrl + Z` | Undo last point. |
97
+ | `Spacebar` | Finalize and save current AI segment. |
98
+ | `Enter` | **Save final mask for the current image to a `.npz` file.** |
99
+ | `M` | **Merge** selected segments into a single class. |
100
+ | `V` / `Delete` / `Backspace`| **Delete** selected segments. |
101
+ | `C` | Clear temporary points/vertices. |
102
+ | `W/A/S/D` | Pan image. |
103
+ | `Scroll Wheel` | Zoom-in or -out. |
104
+
105
+ ---
106
+
107
+ ## 📦 Output Format
108
+
109
+ LazyLabel saves your work as a compressed NumPy array (`.npz`) with the same name as your image file.
110
+
111
+ The file contains a single data key, `'mask'`, holding a **one-hot encoded tensor** with the shape `(H, W, C)`:
112
+ * `H`: Image height.
113
+ * `W`: Image width.
114
+ * `C`: Total unique classes.
115
+
116
+ Each channel is a binary mask for a class, combining all assigned segments into a clean, ML-ready output.
117
+
118
+ ---
119
+
120
+ ## 🏗️ Architecture
121
+
122
+ LazyLabel features a modern, modular architecture designed for maintainability and extensibility:
123
+
124
+ * **Modular Design**: Clean separation between UI, business logic, and configuration
125
+ * **Signal-Based Communication**: Loose coupling between components using PyQt signals
126
+ * **Persistent Configuration**: User settings and preferences saved between sessions
127
+ * **Extensible Model System**: Easy integration of new SAM models and types
128
+
129
+ For detailed technical documentation, see [ARCHITECTURE.md](src/lazylabel/ARCHITECTURE.md).
130
+
131
+ ---
132
+
133
+ ## ⌨️ Hotkey Customization
134
+
135
+ LazyLabel includes a comprehensive hotkey management system:
136
+
137
+ * **Full Customization**: Personalize keyboard shortcuts for all 27+ functions
138
+ * **Category Organization**: Hotkeys organized by function (Modes, Actions, Navigation, etc.)
139
+ * **Primary & Secondary Keys**: Set multiple shortcuts for the same action
140
+ * **Persistent Settings**: Custom hotkeys saved between sessions
141
+ * **Conflict Prevention**: System prevents duplicate key assignments
142
+
143
+ For complete hotkey documentation, see [HOTKEY_FEATURE.md](src/lazylabel/HOTKEY_FEATURE.md).
144
+
145
+ ---
146
+
147
+ ## ☕ Support LazyLabel
148
+ [If you found LazyLabel helpful, consider supporting the project!](https://buymeacoffee.com/dnzckn)
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lazylabel-gui"
7
- version = "1.0.8"
7
+ version = "1.1.0"
8
8
  authors = [
9
9
  { name="Deniz N. Cakan", email="deniz.n.cakan@gmail.com" },
10
10
  ]
11
- description = "An image segmentation GUI for generating mask tensors."
11
+ description = "An image segmentation GUI for generating ML ready mask tensors and annotations."
12
12
  readme = "README.md"
13
13
  license = { file="LICENSE" }
14
14
  requires-python = ">=3.10"
@@ -0,0 +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']
@@ -0,0 +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']
@@ -0,0 +1,169 @@
1
+ """Hotkey management system."""
2
+
3
+ import json
4
+ import os
5
+ from dataclasses import dataclass, asdict
6
+ from typing import Dict, Optional, List, Tuple
7
+ from PyQt6.QtCore import Qt
8
+ from PyQt6.QtGui import QKeySequence
9
+
10
+
11
+ @dataclass
12
+ class HotkeyAction:
13
+ """Represents a hotkey action with primary and secondary keys."""
14
+ name: str
15
+ description: str
16
+ primary_key: str
17
+ secondary_key: Optional[str] = None
18
+ category: str = "General"
19
+ mouse_related: bool = False # Cannot be reassigned if True
20
+
21
+
22
+ class HotkeyManager:
23
+ """Manages application hotkeys with persistence."""
24
+
25
+ def __init__(self, config_dir: str):
26
+ self.config_dir = config_dir
27
+ self.hotkeys_file = os.path.join(config_dir, "hotkeys.json")
28
+ self.actions: Dict[str, HotkeyAction] = {}
29
+ self._initialize_default_hotkeys()
30
+ self.load_hotkeys()
31
+
32
+ def _initialize_default_hotkeys(self):
33
+ """Initialize default hotkey mappings."""
34
+ default_hotkeys = [
35
+ # Navigation
36
+ HotkeyAction("load_next_image", "Load Next Image", "Right", category="Navigation"),
37
+ HotkeyAction("load_previous_image", "Load Previous Image", "Left", category="Navigation"),
38
+ HotkeyAction("fit_view", "Fit View", "Period", category="Navigation"),
39
+
40
+ # Modes
41
+ HotkeyAction("sam_mode", "Point Mode (SAM)", "1", category="Modes"),
42
+ HotkeyAction("polygon_mode", "Polygon Mode", "2", category="Modes"),
43
+ HotkeyAction("selection_mode", "Selection Mode", "E", category="Modes"),
44
+ HotkeyAction("pan_mode", "Pan Mode", "Q", category="Modes"),
45
+ HotkeyAction("edit_mode", "Edit Mode", "R", category="Modes"),
46
+
47
+ # Actions
48
+ HotkeyAction("clear_points", "Clear Points/Vertices", "C", category="Actions"),
49
+ HotkeyAction("save_segment", "Save Current Segment", "Space", category="Actions"),
50
+ HotkeyAction("save_output", "Save Output", "Return", category="Actions"),
51
+ HotkeyAction("save_output_alt", "Save Output (Alt)", "Enter", category="Actions"),
52
+ HotkeyAction("undo", "Undo Last Action", "Ctrl+Z", category="Actions"),
53
+ HotkeyAction("escape", "Cancel/Clear Selection", "Escape", category="Actions"),
54
+
55
+ # 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
+
61
+ # View
62
+ HotkeyAction("zoom_in", "Zoom In", "Ctrl+Plus", category="View"),
63
+ HotkeyAction("zoom_out", "Zoom Out", "Ctrl+Minus", category="View"),
64
+
65
+ # Movement (WASD)
66
+ HotkeyAction("pan_up", "Pan Up", "W", category="Movement"),
67
+ HotkeyAction("pan_down", "Pan Down", "S", category="Movement"),
68
+ HotkeyAction("pan_left", "Pan Left", "A", category="Movement"),
69
+ HotkeyAction("pan_right", "Pan Right", "D", category="Movement"),
70
+
71
+ # 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),
78
+ ]
79
+
80
+ for action in default_hotkeys:
81
+ self.actions[action.name] = action
82
+
83
+ def get_action(self, action_name: str) -> Optional[HotkeyAction]:
84
+ """Get hotkey action by name."""
85
+ return self.actions.get(action_name)
86
+
87
+ def get_actions_by_category(self) -> Dict[str, List[HotkeyAction]]:
88
+ """Get actions grouped by category."""
89
+ categories = {}
90
+ for action in self.actions.values():
91
+ if action.category not in categories:
92
+ categories[action.category] = []
93
+ categories[action.category].append(action)
94
+ return categories
95
+
96
+ def set_primary_key(self, action_name: str, key: str) -> bool:
97
+ """Set primary key for an action."""
98
+ if action_name in self.actions and not self.actions[action_name].mouse_related:
99
+ self.actions[action_name].primary_key = key
100
+ return True
101
+ return False
102
+
103
+ def set_secondary_key(self, action_name: str, key: Optional[str]) -> bool:
104
+ """Set secondary key for an action."""
105
+ if action_name in self.actions and not self.actions[action_name].mouse_related:
106
+ self.actions[action_name].secondary_key = key
107
+ return True
108
+ return False
109
+
110
+ def get_key_for_action(self, action_name: str) -> Tuple[Optional[str], Optional[str]]:
111
+ """Get primary and secondary keys for an action."""
112
+ action = self.actions.get(action_name)
113
+ if action:
114
+ return action.primary_key, action.secondary_key
115
+ return None, None
116
+
117
+ def is_key_in_use(self, key: str, exclude_action: str = None) -> Optional[str]:
118
+ """Check if a key is already in use by another action."""
119
+ for name, action in self.actions.items():
120
+ if name == exclude_action:
121
+ continue
122
+ if action.primary_key == key or action.secondary_key == key:
123
+ return name
124
+ return None
125
+
126
+ def reset_to_defaults(self):
127
+ """Reset all hotkeys to default values."""
128
+ self._initialize_default_hotkeys()
129
+
130
+ def save_hotkeys(self):
131
+ """Save hotkeys to file."""
132
+ os.makedirs(self.config_dir, exist_ok=True)
133
+
134
+ # Convert to serializable format
135
+ data = {}
136
+ for name, action in self.actions.items():
137
+ if not action.mouse_related: # Don't save mouse-related actions
138
+ data[name] = {
139
+ 'primary_key': action.primary_key,
140
+ 'secondary_key': action.secondary_key
141
+ }
142
+
143
+ with open(self.hotkeys_file, 'w') as f:
144
+ json.dump(data, f, indent=4)
145
+
146
+ def load_hotkeys(self):
147
+ """Load hotkeys from file."""
148
+ if not os.path.exists(self.hotkeys_file):
149
+ return
150
+
151
+ try:
152
+ with open(self.hotkeys_file, 'r') as f:
153
+ data = json.load(f)
154
+
155
+ for name, keys in data.items():
156
+ 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')
159
+ except (json.JSONDecodeError, KeyError, FileNotFoundError):
160
+ # If loading fails, keep defaults
161
+ pass
162
+
163
+ def key_sequence_to_string(self, key_sequence: QKeySequence) -> str:
164
+ """Convert QKeySequence to string representation."""
165
+ return key_sequence.toString()
166
+
167
+ def string_to_key_sequence(self, key_string: str) -> QKeySequence:
168
+ """Convert string to QKeySequence."""
169
+ return QKeySequence(key_string)
@@ -0,0 +1,41 @@
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
@@ -0,0 +1,66 @@
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()
@@ -0,0 +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']