lazylabel-gui 1.3.6__tar.gz → 1.3.7__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 (63) hide show
  1. lazylabel_gui-1.3.7/PKG-INFO +227 -0
  2. lazylabel_gui-1.3.7/README.md +172 -0
  3. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/pyproject.toml +1 -1
  4. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/main_window.py +27 -14
  5. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/modes/multi_view_mode.py +38 -10
  6. lazylabel_gui-1.3.7/src/lazylabel_gui.egg-info/PKG-INFO +227 -0
  7. lazylabel_gui-1.3.6/PKG-INFO +0 -197
  8. lazylabel_gui-1.3.6/README.md +0 -142
  9. lazylabel_gui-1.3.6/src/lazylabel_gui.egg-info/PKG-INFO +0 -197
  10. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/LICENSE +0 -0
  11. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/setup.cfg +0 -0
  12. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/__init__.py +0 -0
  13. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/__main__.py +0 -0
  14. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/config/__init__.py +0 -0
  15. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/config/hotkeys.py +0 -0
  16. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/config/paths.py +0 -0
  17. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/config/settings.py +0 -0
  18. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/core/__init__.py +0 -0
  19. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/core/file_manager.py +0 -0
  20. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/core/model_manager.py +0 -0
  21. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/core/segment_manager.py +0 -0
  22. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/main.py +0 -0
  23. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/models/__init__.py +0 -0
  24. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/models/sam2_model.py +0 -0
  25. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/models/sam_model.py +0 -0
  26. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/__init__.py +0 -0
  27. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/control_panel.py +0 -0
  28. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/editable_vertex.py +0 -0
  29. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/hotkey_dialog.py +0 -0
  30. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/hoverable_pixelmap_item.py +0 -0
  31. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/hoverable_polygon_item.py +0 -0
  32. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/modes/__init__.py +0 -0
  33. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/modes/base_mode.py +0 -0
  34. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/modes/single_view_mode.py +0 -0
  35. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/numeric_table_widget_item.py +0 -0
  36. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/photo_viewer.py +0 -0
  37. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/reorderable_class_table.py +0 -0
  38. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/right_panel.py +0 -0
  39. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/widgets/__init__.py +0 -0
  40. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/widgets/adjustments_widget.py +0 -0
  41. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/widgets/border_crop_widget.py +0 -0
  42. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/widgets/channel_threshold_widget.py +0 -0
  43. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/widgets/fft_threshold_widget.py +0 -0
  44. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/widgets/fragment_threshold_widget.py +0 -0
  45. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/widgets/model_selection_widget.py +0 -0
  46. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/widgets/settings_widget.py +0 -0
  47. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/widgets/status_bar.py +0 -0
  48. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/workers/__init__.py +0 -0
  49. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/workers/image_discovery_worker.py +0 -0
  50. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/workers/multi_view_sam_init_worker.py +0 -0
  51. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/workers/multi_view_sam_update_worker.py +0 -0
  52. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/workers/sam_update_worker.py +0 -0
  53. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/ui/workers/single_view_sam_init_worker.py +0 -0
  54. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/utils/__init__.py +0 -0
  55. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/utils/custom_file_system_model.py +0 -0
  56. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/utils/fast_file_manager.py +0 -0
  57. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/utils/logger.py +0 -0
  58. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel/utils/utils.py +0 -0
  59. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel_gui.egg-info/SOURCES.txt +0 -0
  60. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel_gui.egg-info/dependency_links.txt +0 -0
  61. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel_gui.egg-info/entry_points.txt +0 -0
  62. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel_gui.egg-info/requires.txt +0 -0
  63. {lazylabel_gui-1.3.6 → lazylabel_gui-1.3.7}/src/lazylabel_gui.egg-info/top_level.txt +0 -0
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: lazylabel-gui
3
+ Version: 1.3.7
4
+ Summary: An image segmentation GUI for generating ML ready mask tensors and annotations.
5
+ Author-email: "Deniz N. Cakan" <deniz.n.cakan@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Deniz N. Cakan
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/dnzckn/lazylabel
29
+ Project-URL: Bug Tracker, https://github.com/dnzckn/lazylabel/issues
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
34
+ Classifier: Environment :: X11 Applications :: Qt
35
+ Requires-Python: >=3.10
36
+ Description-Content-Type: text/markdown
37
+ License-File: LICENSE
38
+ Requires-Dist: PyQt6>=6.9.0
39
+ Requires-Dist: pyqtdarktheme==2.1.0
40
+ Requires-Dist: torch>=2.7.1
41
+ Requires-Dist: torchvision>=0.22.1
42
+ Requires-Dist: segment-anything==1.0
43
+ Requires-Dist: numpy>=2.1.2
44
+ Requires-Dist: opencv-python>=4.11.0.86
45
+ Requires-Dist: scipy>=1.15.3
46
+ Requires-Dist: requests>=2.32.4
47
+ Requires-Dist: tqdm>=4.67.1
48
+ Provides-Extra: dev
49
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
50
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
51
+ Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
52
+ Requires-Dist: pytest-qt>=4.2.0; extra == "dev"
53
+ Requires-Dist: ruff>=0.8.0; extra == "dev"
54
+ Dynamic: license-file
55
+
56
+ # LazyLabel
57
+
58
+ <div align="center">
59
+ <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo2.png" alt="LazyLabel Logo" style="height:60px; vertical-align:middle;" />
60
+ <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;" />
61
+ </div>
62
+
63
+ **AI-Assisted Image Segmentation for Machine Learning Dataset Preparation**
64
+
65
+ LazyLabel combines Meta's Segment Anything Model (SAM) with comprehensive manual annotation tools to accelerate the creation of pixel-perfect segmentation masks for computer vision applications.
66
+
67
+ <div align="center">
68
+ <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/gui.PNG" alt="LazyLabel Screenshot" width="800"/>
69
+ </div>
70
+
71
+ ---
72
+
73
+ ## Quick Start
74
+
75
+ ```bash
76
+ pip install lazylabel-gui
77
+ lazylabel-gui
78
+ ```
79
+
80
+ **From source:**
81
+ ```bash
82
+ git clone https://github.com/dnzckn/LazyLabel.git
83
+ cd LazyLabel
84
+ pip install -e .
85
+ lazylabel-gui
86
+ ```
87
+
88
+ **Requirements:** Python 3.10+, 8GB RAM, ~2.5GB disk space (for model weights)
89
+
90
+ ---
91
+
92
+ ## Core Features
93
+
94
+ ### AI-Powered Segmentation
95
+ LazyLabel leverages Meta's SAM for intelligent object detection:
96
+ - Single-click object segmentation
97
+ - Interactive refinement with positive/negative points
98
+ - Support for both SAM 1.0 and SAM 2.1 models
99
+ - GPU acceleration with automatic CPU fallback
100
+
101
+ ### Manual Annotation Tools
102
+ When precision matters:
103
+ - Polygon drawing with vertex-level editing
104
+ - Bounding box annotations for object detection
105
+ - Edit mode for adjusting existing segments
106
+ - Merge tool for combining related segments
107
+
108
+ ### Image Processing & Filtering
109
+ Advanced preprocessing capabilities:
110
+ - **FFT filtering**: Remove noise and enhance edges
111
+ - **Channel thresholding**: Isolate objects by color
112
+ - **Border cropping**: Define crop regions that set pixels outside the area to zero in saved outputs
113
+ - **View adjustments**: Brightness, contrast, gamma correction
114
+
115
+ ### Multi-View Mode
116
+ Process multiple images efficiently:
117
+ - Annotate up to 4 images simultaneously
118
+ - Synchronized zoom and pan across views
119
+ - Mirror annotations to all linked images
120
+
121
+ ---
122
+
123
+ ## Export Formats
124
+
125
+ ### NPZ Format (Semantic Segmentation)
126
+ One-hot encoded masks optimized for deep learning:
127
+
128
+ ```python
129
+ import numpy as np
130
+
131
+ data = np.load('image.npz')
132
+ mask = data['mask'] # Shape: (height, width, num_classes)
133
+
134
+ # Each channel represents one class
135
+ sky = mask[:, :, 0]
136
+ boats = mask[:, :, 1]
137
+ cats = mask[:, :, 2]
138
+ dogs = mask[:, :, 3]
139
+ ```
140
+
141
+ ### YOLO Format (Object Detection)
142
+ Normalized polygon coordinates for YOLO training:
143
+ ```
144
+ 0 0.234 0.456 0.289 0.478 0.301 0.523 ...
145
+ 1 0.567 0.123 0.598 0.145 0.612 0.189 ...
146
+ ```
147
+
148
+ ### Class Aliases (JSON)
149
+ Maintains consistent class naming across datasets:
150
+ ```json
151
+ {
152
+ "0": "background",
153
+ "1": "person",
154
+ "2": "vehicle"
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Typical Workflow
161
+
162
+ 1. **Open folder** containing your images
163
+ 2. **Click objects** to generate AI masks (mode 1)
164
+ 3. **Refine** with additional points or manual tools
165
+ 4. **Assign classes** and organize in the class table
166
+ 5. **Export** as NPZ or YOLO format
167
+
168
+ ### Advanced Preprocessing Workflow
169
+
170
+ For challenging images:
171
+ 1. Apply **FFT filtering** to reduce noise
172
+ 2. Use **channel thresholding** to isolate color ranges
173
+ 3. Enable **"Operate on View"** to pass filtered images to SAM
174
+ 4. Fine-tune with manual tools
175
+
176
+ ---
177
+
178
+ ## Advanced Features
179
+
180
+ ### Multi-View Mode
181
+ Access via the "Multi" tab to process multiple images:
182
+ - 2-view (side-by-side) or 4-view (grid) layouts
183
+ - Annotations mirror across linked views automatically
184
+ - Synchronized zoom maintains alignment
185
+
186
+ ### SAM 2.1 Support
187
+ LazyLabel supports both SAM 1.0 (default) and SAM 2.1 models. SAM 2.1 offers improved segmentation accuracy and better handling of complex boundaries.
188
+
189
+ To use SAM 2.1 models:
190
+ 1. Install the SAM 2 package:
191
+ ```bash
192
+ pip install git+https://github.com/facebookresearch/sam2.git
193
+ ```
194
+ 2. Download a SAM 2.1 model (e.g., `sam2.1_hiera_large.pt`) from the [SAM 2 repository](https://github.com/facebookresearch/sam2)
195
+ 3. Place the model file in LazyLabel's models folder:
196
+ - If installed via pip: `~/.local/share/lazylabel/models/` (or equivalent on your system)
197
+ - If running from source: `src/lazylabel/models/`
198
+ 4. Select the SAM 2.1 model from the dropdown in LazyLabel's settings
199
+
200
+ Note: SAM 1.0 models are automatically downloaded on first use.
201
+
202
+ ---
203
+
204
+ ## Key Shortcuts
205
+
206
+ | Action | Key | Description |
207
+ |--------|-----|-------------|
208
+ | AI Mode | `1` | SAM point-click segmentation |
209
+ | Draw Mode | `2` | Manual polygon creation |
210
+ | Edit Mode | `E` | Modify existing segments |
211
+ | Accept AI Segment | `Space` | Confirm AI segment suggestion |
212
+ | Save | `Enter` | Save annotations |
213
+ | Merge | `M` | Combine selected segments |
214
+ | Pan Mode | `Q` | Enter pan mode |
215
+ | Pan | `WASD` | Navigate image |
216
+ | Delete | `V`/`Delete` | Remove segments |
217
+ | Undo/Redo | `Ctrl+Z/Y` | Action history |
218
+
219
+ ---
220
+
221
+ ## Documentation
222
+
223
+ - [Usage Manual](src/lazylabel/USAGE_MANUAL.md) - Comprehensive feature guide
224
+ - [Architecture Guide](src/lazylabel/ARCHITECTURE.md) - Technical implementation details
225
+ - [GitHub Issues](https://github.com/dnzckn/LazyLabel/issues) - Report bugs or request features
226
+
227
+ ---
@@ -0,0 +1,172 @@
1
+ # LazyLabel
2
+
3
+ <div align="center">
4
+ <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo2.png" alt="LazyLabel Logo" style="height:60px; vertical-align:middle;" />
5
+ <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;" />
6
+ </div>
7
+
8
+ **AI-Assisted Image Segmentation for Machine Learning Dataset Preparation**
9
+
10
+ LazyLabel combines Meta's Segment Anything Model (SAM) with comprehensive manual annotation tools to accelerate the creation of pixel-perfect segmentation masks for computer vision applications.
11
+
12
+ <div align="center">
13
+ <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/gui.PNG" alt="LazyLabel Screenshot" width="800"/>
14
+ </div>
15
+
16
+ ---
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ pip install lazylabel-gui
22
+ lazylabel-gui
23
+ ```
24
+
25
+ **From source:**
26
+ ```bash
27
+ git clone https://github.com/dnzckn/LazyLabel.git
28
+ cd LazyLabel
29
+ pip install -e .
30
+ lazylabel-gui
31
+ ```
32
+
33
+ **Requirements:** Python 3.10+, 8GB RAM, ~2.5GB disk space (for model weights)
34
+
35
+ ---
36
+
37
+ ## Core Features
38
+
39
+ ### AI-Powered Segmentation
40
+ LazyLabel leverages Meta's SAM for intelligent object detection:
41
+ - Single-click object segmentation
42
+ - Interactive refinement with positive/negative points
43
+ - Support for both SAM 1.0 and SAM 2.1 models
44
+ - GPU acceleration with automatic CPU fallback
45
+
46
+ ### Manual Annotation Tools
47
+ When precision matters:
48
+ - Polygon drawing with vertex-level editing
49
+ - Bounding box annotations for object detection
50
+ - Edit mode for adjusting existing segments
51
+ - Merge tool for combining related segments
52
+
53
+ ### Image Processing & Filtering
54
+ Advanced preprocessing capabilities:
55
+ - **FFT filtering**: Remove noise and enhance edges
56
+ - **Channel thresholding**: Isolate objects by color
57
+ - **Border cropping**: Define crop regions that set pixels outside the area to zero in saved outputs
58
+ - **View adjustments**: Brightness, contrast, gamma correction
59
+
60
+ ### Multi-View Mode
61
+ Process multiple images efficiently:
62
+ - Annotate up to 4 images simultaneously
63
+ - Synchronized zoom and pan across views
64
+ - Mirror annotations to all linked images
65
+
66
+ ---
67
+
68
+ ## Export Formats
69
+
70
+ ### NPZ Format (Semantic Segmentation)
71
+ One-hot encoded masks optimized for deep learning:
72
+
73
+ ```python
74
+ import numpy as np
75
+
76
+ data = np.load('image.npz')
77
+ mask = data['mask'] # Shape: (height, width, num_classes)
78
+
79
+ # Each channel represents one class
80
+ sky = mask[:, :, 0]
81
+ boats = mask[:, :, 1]
82
+ cats = mask[:, :, 2]
83
+ dogs = mask[:, :, 3]
84
+ ```
85
+
86
+ ### YOLO Format (Object Detection)
87
+ Normalized polygon coordinates for YOLO training:
88
+ ```
89
+ 0 0.234 0.456 0.289 0.478 0.301 0.523 ...
90
+ 1 0.567 0.123 0.598 0.145 0.612 0.189 ...
91
+ ```
92
+
93
+ ### Class Aliases (JSON)
94
+ Maintains consistent class naming across datasets:
95
+ ```json
96
+ {
97
+ "0": "background",
98
+ "1": "person",
99
+ "2": "vehicle"
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Typical Workflow
106
+
107
+ 1. **Open folder** containing your images
108
+ 2. **Click objects** to generate AI masks (mode 1)
109
+ 3. **Refine** with additional points or manual tools
110
+ 4. **Assign classes** and organize in the class table
111
+ 5. **Export** as NPZ or YOLO format
112
+
113
+ ### Advanced Preprocessing Workflow
114
+
115
+ For challenging images:
116
+ 1. Apply **FFT filtering** to reduce noise
117
+ 2. Use **channel thresholding** to isolate color ranges
118
+ 3. Enable **"Operate on View"** to pass filtered images to SAM
119
+ 4. Fine-tune with manual tools
120
+
121
+ ---
122
+
123
+ ## Advanced Features
124
+
125
+ ### Multi-View Mode
126
+ Access via the "Multi" tab to process multiple images:
127
+ - 2-view (side-by-side) or 4-view (grid) layouts
128
+ - Annotations mirror across linked views automatically
129
+ - Synchronized zoom maintains alignment
130
+
131
+ ### SAM 2.1 Support
132
+ LazyLabel supports both SAM 1.0 (default) and SAM 2.1 models. SAM 2.1 offers improved segmentation accuracy and better handling of complex boundaries.
133
+
134
+ To use SAM 2.1 models:
135
+ 1. Install the SAM 2 package:
136
+ ```bash
137
+ pip install git+https://github.com/facebookresearch/sam2.git
138
+ ```
139
+ 2. Download a SAM 2.1 model (e.g., `sam2.1_hiera_large.pt`) from the [SAM 2 repository](https://github.com/facebookresearch/sam2)
140
+ 3. Place the model file in LazyLabel's models folder:
141
+ - If installed via pip: `~/.local/share/lazylabel/models/` (or equivalent on your system)
142
+ - If running from source: `src/lazylabel/models/`
143
+ 4. Select the SAM 2.1 model from the dropdown in LazyLabel's settings
144
+
145
+ Note: SAM 1.0 models are automatically downloaded on first use.
146
+
147
+ ---
148
+
149
+ ## Key Shortcuts
150
+
151
+ | Action | Key | Description |
152
+ |--------|-----|-------------|
153
+ | AI Mode | `1` | SAM point-click segmentation |
154
+ | Draw Mode | `2` | Manual polygon creation |
155
+ | Edit Mode | `E` | Modify existing segments |
156
+ | Accept AI Segment | `Space` | Confirm AI segment suggestion |
157
+ | Save | `Enter` | Save annotations |
158
+ | Merge | `M` | Combine selected segments |
159
+ | Pan Mode | `Q` | Enter pan mode |
160
+ | Pan | `WASD` | Navigate image |
161
+ | Delete | `V`/`Delete` | Remove segments |
162
+ | Undo/Redo | `Ctrl+Z/Y` | Action history |
163
+
164
+ ---
165
+
166
+ ## Documentation
167
+
168
+ - [Usage Manual](src/lazylabel/USAGE_MANUAL.md) - Comprehensive feature guide
169
+ - [Architecture Guide](src/lazylabel/ARCHITECTURE.md) - Technical implementation details
170
+ - [GitHub Issues](https://github.com/dnzckn/LazyLabel/issues) - Report bugs or request features
171
+
172
+ ---
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lazylabel-gui"
7
- version = "1.3.6"
7
+ version = "1.3.7"
8
8
  authors = [
9
9
  { name="Deniz N. Cakan", email="deniz.n.cakan@gmail.com" },
10
10
  ]
@@ -1582,6 +1582,19 @@ class MainWindow(QMainWindow):
1582
1582
  # Reset AI mode state
1583
1583
  self.ai_click_start_pos = None
1584
1584
 
1585
+ # Reset all link states to linked when navigating to new images
1586
+ config = self._get_multi_view_config()
1587
+ num_viewers = config["num_viewers"]
1588
+ self.multi_view_linked = [True] * num_viewers
1589
+
1590
+ # Reset unlink button appearances to default linked state
1591
+ if hasattr(self, "multi_view_unlink_buttons"):
1592
+ for i, button in enumerate(self.multi_view_unlink_buttons):
1593
+ if i < num_viewers:
1594
+ button.setText("X")
1595
+ button.setToolTip("Unlink this image from mirroring")
1596
+ button.setStyleSheet("")
1597
+
1585
1598
  # Update UI lists to reflect cleared state
1586
1599
  self._update_all_lists()
1587
1600
 
@@ -2829,7 +2842,8 @@ class MainWindow(QMainWindow):
2829
2842
  for i in range(num_viewers):
2830
2843
  image_path = self.multi_view_images[i]
2831
2844
 
2832
- if not image_path:
2845
+ # Skip if no image or if this viewer is unlinked
2846
+ if not image_path or not self.multi_view_linked[i]:
2833
2847
  continue
2834
2848
 
2835
2849
  # Filter segments for this viewer
@@ -6802,21 +6816,20 @@ class MainWindow(QMainWindow):
6802
6816
 
6803
6817
  def _toggle_multi_view_link(self, image_index):
6804
6818
  """Toggle the link status for a specific image in multi-view."""
6805
- if 0 <= image_index < 2:
6806
- self.multi_view_linked[image_index] = not self.multi_view_linked[
6807
- image_index
6808
- ]
6819
+ # Check bounds and link status - only allow unlinking when currently linked
6820
+ if (
6821
+ 0 <= image_index < len(self.multi_view_linked)
6822
+ and self.multi_view_linked[image_index]
6823
+ ):
6824
+ # Currently linked - allow unlinking
6825
+ self.multi_view_linked[image_index] = False
6809
6826
 
6810
- # Update button appearance
6827
+ # Update button appearance to show unlinked state
6811
6828
  button = self.multi_view_unlink_buttons[image_index]
6812
- if self.multi_view_linked[image_index]:
6813
- button.setText("X")
6814
- button.setToolTip("Unlink this image from mirroring")
6815
- button.setStyleSheet("")
6816
- else:
6817
- button.setText("↪")
6818
- button.setToolTip("Link this image to mirroring")
6819
- button.setStyleSheet("background-color: #ffcccc;")
6829
+ button.setText("↪")
6830
+ button.setToolTip("This image is unlinked from mirroring")
6831
+ button.setStyleSheet("background-color: #ff4444; color: white;")
6832
+ # If already unlinked or invalid index, do nothing (prevent re-linking)
6820
6833
 
6821
6834
  def _start_background_image_discovery(self):
6822
6835
  """Start background discovery of all image files."""
@@ -369,12 +369,20 @@ class MultiViewModeHandler(BaseModeHandler):
369
369
 
370
370
  paired_segment = {"type": "Polygon", "views": {}}
371
371
 
372
- # Add view data for all viewers with same coordinates
372
+ # Add view data for current viewer and mirror to linked viewers only
373
+ paired_segment["views"][viewer_index] = view_data
374
+
375
+ # Mirror to other viewers only if they are linked
373
376
  for viewer_idx in range(num_viewers):
374
- paired_segment["views"][viewer_idx] = {
375
- "vertices": view_data["vertices"].copy(),
376
- "mask": None,
377
- }
377
+ if (
378
+ viewer_idx != viewer_index
379
+ and self.main_window.multi_view_linked[viewer_idx]
380
+ and self.main_window.multi_view_images[viewer_idx] is not None
381
+ ):
382
+ paired_segment["views"][viewer_idx] = {
383
+ "vertices": view_data["vertices"].copy(),
384
+ "mask": None,
385
+ }
378
386
 
379
387
  # Add to segment manager
380
388
  self.main_window.segment_manager.add_segment(paired_segment)
@@ -783,9 +791,13 @@ class MultiViewModeHandler(BaseModeHandler):
783
791
  # Add the current viewer's data
784
792
  paired_segment["views"][viewer_index] = view_data
785
793
 
786
- # Mirror to all other viewers with same coordinates (they should align between linked images)
794
+ # Mirror to all other viewers with same coordinates (only if they are linked)
787
795
  for other_viewer_index in range(num_viewers):
788
- if other_viewer_index != viewer_index:
796
+ if (
797
+ other_viewer_index != viewer_index
798
+ and self.main_window.multi_view_linked[other_viewer_index]
799
+ and self.main_window.multi_view_images[other_viewer_index] is not None
800
+ ):
789
801
  mirrored_view_data = {
790
802
  "vertices": view_data[
791
803
  "vertices"
@@ -807,11 +819,27 @@ class MultiViewModeHandler(BaseModeHandler):
807
819
 
808
820
  # Update UI
809
821
  self.main_window._update_all_lists()
810
- viewer_count_text = "all viewers" if num_viewers > 2 else "both viewers"
811
- self.main_window._show_notification(
812
- f"Polygon created and mirrored to {viewer_count_text}."
822
+
823
+ # Count linked viewers (excluding the source viewer)
824
+ linked_viewers_count = sum(
825
+ 1
826
+ for i in range(num_viewers)
827
+ if i != viewer_index
828
+ and self.main_window.multi_view_linked[i]
829
+ and self.main_window.multi_view_images[i] is not None
813
830
  )
814
831
 
832
+ if linked_viewers_count == 0:
833
+ viewer_count_text = "created (no linked viewers to mirror to)"
834
+ elif linked_viewers_count == 1:
835
+ viewer_count_text = "created and mirrored to 1 linked viewer"
836
+ else:
837
+ viewer_count_text = (
838
+ f"created and mirrored to {linked_viewers_count} linked viewers"
839
+ )
840
+
841
+ self.main_window._show_notification(f"Polygon {viewer_count_text}.")
842
+
815
843
  # Clear polygon state for this viewer
816
844
  self._clear_multi_view_polygon(viewer_index)
817
845