lazylabel-gui 1.0.9__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.
- {lazylabel_gui-1.0.9 → lazylabel_gui-1.1.0}/PKG-INFO +61 -11
- lazylabel_gui-1.1.0/README.md +148 -0
- {lazylabel_gui-1.0.9 → lazylabel_gui-1.1.0}/pyproject.toml +1 -1
- lazylabel_gui-1.1.0/src/lazylabel/__init__.py +9 -0
- lazylabel_gui-1.1.0/src/lazylabel/config/__init__.py +7 -0
- lazylabel_gui-1.1.0/src/lazylabel/config/hotkeys.py +169 -0
- lazylabel_gui-1.1.0/src/lazylabel/config/paths.py +41 -0
- lazylabel_gui-1.1.0/src/lazylabel/config/settings.py +66 -0
- lazylabel_gui-1.1.0/src/lazylabel/core/__init__.py +7 -0
- lazylabel_gui-1.1.0/src/lazylabel/core/file_manager.py +106 -0
- lazylabel_gui-1.1.0/src/lazylabel/core/model_manager.py +94 -0
- lazylabel_gui-1.1.0/src/lazylabel/core/segment_manager.py +140 -0
- lazylabel_gui-1.1.0/src/lazylabel/main.py +22 -0
- lazylabel_gui-1.1.0/src/lazylabel/models/__init__.py +5 -0
- lazylabel_gui-1.1.0/src/lazylabel/models/sam_model.py +154 -0
- lazylabel_gui-1.1.0/src/lazylabel/ui/__init__.py +8 -0
- lazylabel_gui-1.1.0/src/lazylabel/ui/control_panel.py +220 -0
- {lazylabel_gui-1.0.9/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/editable_vertex.py +25 -3
- lazylabel_gui-1.1.0/src/lazylabel/ui/hotkey_dialog.py +384 -0
- {lazylabel_gui-1.0.9/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/hoverable_polygon_item.py +17 -1
- lazylabel_gui-1.1.0/src/lazylabel/ui/main_window.py +1264 -0
- lazylabel_gui-1.1.0/src/lazylabel/ui/right_panel.py +239 -0
- lazylabel_gui-1.1.0/src/lazylabel/ui/widgets/__init__.py +7 -0
- lazylabel_gui-1.1.0/src/lazylabel/ui/widgets/adjustments_widget.py +107 -0
- lazylabel_gui-1.1.0/src/lazylabel/ui/widgets/model_selection_widget.py +94 -0
- lazylabel_gui-1.1.0/src/lazylabel/ui/widgets/settings_widget.py +106 -0
- lazylabel_gui-1.1.0/src/lazylabel/utils/__init__.py +6 -0
- {lazylabel_gui-1.0.9/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/utils}/custom_file_system_model.py +9 -3
- {lazylabel_gui-1.0.9 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/PKG-INFO +61 -11
- lazylabel_gui-1.1.0/src/lazylabel_gui.egg-info/SOURCES.txt +39 -0
- lazylabel_gui-1.0.9/README.md +0 -98
- lazylabel_gui-1.0.9/src/lazylabel/controls.py +0 -265
- lazylabel_gui-1.0.9/src/lazylabel/main.py +0 -1278
- lazylabel_gui-1.0.9/src/lazylabel/sam_model.py +0 -70
- lazylabel_gui-1.0.9/src/lazylabel_gui.egg-info/SOURCES.txt +0 -20
- {lazylabel_gui-1.0.9 → lazylabel_gui-1.1.0}/LICENSE +0 -0
- {lazylabel_gui-1.0.9 → lazylabel_gui-1.1.0}/setup.cfg +0 -0
- {lazylabel_gui-1.0.9/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/hoverable_pixelmap_item.py +0 -0
- {lazylabel_gui-1.0.9/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/numeric_table_widget_item.py +0 -0
- {lazylabel_gui-1.0.9/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/photo_viewer.py +0 -0
- {lazylabel_gui-1.0.9/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/ui}/reorderable_class_table.py +0 -0
- {lazylabel_gui-1.0.9/src/lazylabel → lazylabel_gui-1.1.0/src/lazylabel/utils}/utils.py +0 -0
- {lazylabel_gui-1.0.9 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/dependency_links.txt +0 -0
- {lazylabel_gui-1.0.9 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/entry_points.txt +0 -0
- {lazylabel_gui-1.0.9 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/requires.txt +0 -0
- {lazylabel_gui-1.0.9 → lazylabel_gui-1.1.0}/src/lazylabel_gui.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lazylabel-gui
|
3
|
-
Version: 1.0
|
3
|
+
Version: 1.1.0
|
4
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
|
@@ -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
|
-
|
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
|
-
|
62
|
-
*
|
63
|
-
*
|
64
|
-
*
|
65
|
-
|
66
|
-
|
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
|
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
|
-
|
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
|
+

|
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)
|
@@ -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()
|