lazylabel-gui 1.3.6__py3-none-any.whl → 1.3.8__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.
@@ -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
@@ -6279,22 +6293,23 @@ class MainWindow(QMainWindow):
6279
6293
  )
6280
6294
 
6281
6295
  def _start_sequential_multi_view_sam_loading(self):
6282
- """Start loading images into SAM models sequentially to avoid resource conflicts."""
6296
+ """Start loading images into SAM models in parallel for faster processing."""
6283
6297
  # Check if any loading is already in progress to prevent duplicate workers
6284
6298
  any_updating = any(self.multi_view_models_updating)
6285
6299
  if any_updating:
6286
- # Sequential loading already in progress, don't start another
6300
+ # Loading already in progress, don't start another
6287
6301
  updating_indices = [
6288
6302
  i
6289
6303
  for i, updating in enumerate(self.multi_view_models_updating)
6290
6304
  if updating
6291
6305
  ]
6292
6306
  logger.debug(
6293
- f"Sequential loading already in progress for viewers: {updating_indices}"
6307
+ f"Parallel loading already in progress for viewers: {updating_indices}"
6294
6308
  )
6295
6309
  return
6296
6310
 
6297
- # Find the next dirty model that needs updating
6311
+ # Find all dirty models that need updating and start them in parallel
6312
+ models_to_update = []
6298
6313
  for i in range(len(self.multi_view_models)):
6299
6314
  if (
6300
6315
  self.multi_view_images[i]
@@ -6302,15 +6317,24 @@ class MainWindow(QMainWindow):
6302
6317
  and self.multi_view_models_dirty[i]
6303
6318
  and not self.multi_view_models_updating[i]
6304
6319
  ):
6305
- # Start updating this model
6306
- self._ensure_multi_view_sam_updated(i)
6307
- return
6320
+ models_to_update.append(i)
6308
6321
 
6309
- # If no more models to update, we're done
6310
- if self._multi_view_loading_step >= self._multi_view_total_steps:
6311
- self._show_success_notification(
6312
- "AI models ready for prompting", duration=3000
6322
+ if models_to_update:
6323
+ logger.debug(f"Starting parallel loading for viewers: {models_to_update}")
6324
+ # Show notification about parallel loading
6325
+ self._show_notification(
6326
+ f"Loading embeddings for {len(models_to_update)} images in parallel...",
6327
+ duration=0,
6313
6328
  )
6329
+ # Start all workers in parallel
6330
+ for i in models_to_update:
6331
+ self._ensure_multi_view_sam_updated(i)
6332
+ else:
6333
+ # If no more models to update and none are running, we're done
6334
+ if not any(self.multi_view_models_updating):
6335
+ self._show_success_notification(
6336
+ "AI models ready for prompting", duration=3000
6337
+ )
6314
6338
 
6315
6339
  def _on_multi_view_init_error(self, error_message):
6316
6340
  """Handle multi-view model initialization error."""
@@ -6452,14 +6476,8 @@ class MainWindow(QMainWindow):
6452
6476
 
6453
6477
  logger.debug(f"Starting SAM image loading worker for viewer {viewer_index + 1}")
6454
6478
 
6455
- # Show enumerated progress notification
6456
- image_name = Path(image_path).name if image_path else "unknown"
6457
- config = self._get_multi_view_config()
6458
- num_viewers = config["num_viewers"]
6459
- self._show_notification(
6460
- f"Computing embeddings for image {viewer_index + 1}/{num_viewers}: {image_name}",
6461
- duration=0, # Persistent until completion
6462
- )
6479
+ # Individual notifications are now handled by the parallel loading start
6480
+ # No need for per-viewer notifications when loading in parallel
6463
6481
 
6464
6482
  # Get current modified image if operate_on_view is enabled
6465
6483
  current_image = None
@@ -6547,8 +6565,16 @@ class MainWindow(QMainWindow):
6547
6565
  if hasattr(self, "_multi_view_loading_step"):
6548
6566
  self._multi_view_loading_step += 1
6549
6567
 
6550
- # Continue sequential loading
6551
- self._start_sequential_multi_view_sam_loading()
6568
+ # Check if all models are done (either loaded, failed, or timed out)
6569
+ if not any(self.multi_view_models_updating) and not any(
6570
+ self.multi_view_models_dirty
6571
+ ):
6572
+ # All models processed
6573
+ self._show_success_notification("AI model loading complete", duration=3000)
6574
+ elif not any(self.multi_view_models_updating):
6575
+ # No models are currently updating but some may still be dirty
6576
+ # Try to load remaining models
6577
+ self._start_sequential_multi_view_sam_loading()
6552
6578
 
6553
6579
  def _on_multi_view_sam_update_finished(self, viewer_index):
6554
6580
  """Handle completion of multi-view SAM model update."""
@@ -6582,14 +6608,19 @@ class MainWindow(QMainWindow):
6582
6608
  # Update progress
6583
6609
  if hasattr(self, "_multi_view_loading_step"):
6584
6610
  self._multi_view_loading_step += 1
6585
- if self._multi_view_loading_step < self._multi_view_total_steps:
6586
- self._show_notification(
6587
- f"Loading image {self._multi_view_loading_step + 1} of {self._multi_view_total_steps}...",
6588
- duration=0,
6589
- )
6590
6611
 
6591
- # Continue sequential loading of the next SAM model
6592
- self._start_sequential_multi_view_sam_loading()
6612
+ # Check if all models are done loading
6613
+ if not any(self.multi_view_models_updating) and not any(
6614
+ self.multi_view_models_dirty
6615
+ ):
6616
+ # All models loaded successfully
6617
+ self._show_success_notification(
6618
+ "AI models ready for prompting", duration=3000
6619
+ )
6620
+ elif not any(self.multi_view_models_updating):
6621
+ # No models are currently updating but some may still be dirty
6622
+ # This can happen if there was an error, try to load remaining models
6623
+ self._start_sequential_multi_view_sam_loading()
6593
6624
 
6594
6625
  def _on_multi_view_sam_update_error(self, viewer_index, error_message):
6595
6626
  """Handle multi-view SAM model update error."""
@@ -6625,8 +6656,16 @@ class MainWindow(QMainWindow):
6625
6656
  if hasattr(self, "_multi_view_loading_step"):
6626
6657
  self._multi_view_loading_step += 1
6627
6658
 
6628
- # Continue sequential loading even if this model failed
6629
- self._start_sequential_multi_view_sam_loading()
6659
+ # Check if all models are done (either loaded or failed)
6660
+ if not any(self.multi_view_models_updating) and not any(
6661
+ self.multi_view_models_dirty
6662
+ ):
6663
+ # All models processed (some may have failed)
6664
+ self._show_success_notification("AI model loading complete", duration=3000)
6665
+ elif not any(self.multi_view_models_updating):
6666
+ # No models are currently updating but some may still be dirty
6667
+ # Try to load remaining models
6668
+ self._start_sequential_multi_view_sam_loading()
6630
6669
 
6631
6670
  def _cleanup_multi_view_models(self):
6632
6671
  """Clean up multi-view model instances."""
@@ -6802,21 +6841,20 @@ class MainWindow(QMainWindow):
6802
6841
 
6803
6842
  def _toggle_multi_view_link(self, image_index):
6804
6843
  """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
- ]
6844
+ # Check bounds and link status - only allow unlinking when currently linked
6845
+ if (
6846
+ 0 <= image_index < len(self.multi_view_linked)
6847
+ and self.multi_view_linked[image_index]
6848
+ ):
6849
+ # Currently linked - allow unlinking
6850
+ self.multi_view_linked[image_index] = False
6809
6851
 
6810
- # Update button appearance
6852
+ # Update button appearance to show unlinked state
6811
6853
  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;")
6854
+ button.setText("↪")
6855
+ button.setToolTip("This image is unlinked from mirroring")
6856
+ button.setStyleSheet("background-color: #ff4444; color: white;")
6857
+ # If already unlinked or invalid index, do nothing (prevent re-linking)
6820
6858
 
6821
6859
  def _start_background_image_discovery(self):
6822
6860
  """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
 
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: lazylabel-gui
3
+ Version: 1.3.8
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
+ ---
@@ -18,14 +18,14 @@ lazylabel/ui/editable_vertex.py,sha256=ofo3r8ZZ3b8oYV40vgzZuS3QnXYBNzE92ArC2wggJ
18
18
  lazylabel/ui/hotkey_dialog.py,sha256=U_B76HLOxWdWkfA4d2XgRUaZTJPAAE_m5fmwf7Rh-5Y,14743
19
19
  lazylabel/ui/hoverable_pixelmap_item.py,sha256=UbWVxpmCTaeae_AeA8gMOHYGUmAw40fZBFTS3sZlw48,1821
20
20
  lazylabel/ui/hoverable_polygon_item.py,sha256=gZalImJ_PJYM7xON0iiSjQ335ZBREOfSscKLVs-MSh8,2314
21
- lazylabel/ui/main_window.py,sha256=TZ1m7GxxyonhSMffwNe8sE264IzuoML8W-V9r7HOP1E,382538
21
+ lazylabel/ui/main_window.py,sha256=udjyVXL6Ms_zjpT6TILdqvaSkX6u_YyaxWF93O93W2s,384664
22
22
  lazylabel/ui/numeric_table_widget_item.py,sha256=dQUlIFu9syCxTGAHVIlmbgkI7aJ3f3wmDPBz1AGK9Bg,283
23
23
  lazylabel/ui/photo_viewer.py,sha256=3o7Xldn9kJWvWlbpcHDRMk87dnB5xZKbfyAT3oBYlIo,6670
24
24
  lazylabel/ui/reorderable_class_table.py,sha256=sxHhQre5O_MXLDFgKnw43QnvXXoqn5xRKMGitgO7muI,2371
25
25
  lazylabel/ui/right_panel.py,sha256=D69XgPXpLleflsIl8xCtoBZzFleLqx0SezdwfcEJhUg,14280
26
26
  lazylabel/ui/modes/__init__.py,sha256=ikg47aeexLQavSda_3tYn79xGJW38jKoUCLXRe2w8ok,219
27
27
  lazylabel/ui/modes/base_mode.py,sha256=0R3AkjN_WpYwetF3uuOvkTxb6Q1HB-Z1NQPvLh9eUTY,1315
28
- lazylabel/ui/modes/multi_view_mode.py,sha256=TKdqTHokIk267aFVvwXikAtAcuxpG6ezIOlQwoque9c,50606
28
+ lazylabel/ui/modes/multi_view_mode.py,sha256=IiInmNhdFEQAcKuY2QY5ABx-EXgOh1lVeTnJVGoLyNM,51713
29
29
  lazylabel/ui/modes/single_view_mode.py,sha256=khGUXVQ_lv9cCXkOAewQN8iH7R_CPyIVtQjUqEF1eC0,11852
30
30
  lazylabel/ui/widgets/__init__.py,sha256=6VDoMnanqVm3yjOovxie3WggPODuhUsIO75RtxOhQhI,688
31
31
  lazylabel/ui/widgets/adjustments_widget.py,sha256=5xldhdEArX3H2P7EmHPjURdwpV-Wa2WULFfspp0gxns,12750
@@ -47,9 +47,9 @@ lazylabel/utils/custom_file_system_model.py,sha256=-3EimlybvevH6bvqBE0qdFnLADVta
47
47
  lazylabel/utils/fast_file_manager.py,sha256=kzbWz_xKufG5bP6sjyZV1fmOKRWPPNeL-xLYZEu_8wE,44697
48
48
  lazylabel/utils/logger.py,sha256=R7z6ifgA-NY-9ZbLlNH0i19zzwXndJ_gkG2J1zpVEhg,1306
49
49
  lazylabel/utils/utils.py,sha256=sYSCoXL27OaLgOZaUkCAhgmKZ7YfhR3Cc5F8nDIa3Ig,414
50
- lazylabel_gui-1.3.6.dist-info/licenses/LICENSE,sha256=kSDEIgrWAPd1u2UFGGpC9X71dhzrlzBFs8hbDlENnGE,1092
51
- lazylabel_gui-1.3.6.dist-info/METADATA,sha256=wU3SVog5c0CafMdOiUD82IFoQwZ-LUdOTGxbLQWW7WU,7156
52
- lazylabel_gui-1.3.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
- lazylabel_gui-1.3.6.dist-info/entry_points.txt,sha256=Hd0WwEG9OPTa_ziYjiD0aRh7R6Fupt-wdQ3sspdc1mM,54
54
- lazylabel_gui-1.3.6.dist-info/top_level.txt,sha256=YN4uIyrpDBq1wiJaBuZLDipIzyZY0jqJOmmXiPIOUkU,10
55
- lazylabel_gui-1.3.6.dist-info/RECORD,,
50
+ lazylabel_gui-1.3.8.dist-info/licenses/LICENSE,sha256=kSDEIgrWAPd1u2UFGGpC9X71dhzrlzBFs8hbDlENnGE,1092
51
+ lazylabel_gui-1.3.8.dist-info/METADATA,sha256=k6_eCNou42Egim6fW_LuvX3oLd67qTxjVqk2ovHPz0E,7754
52
+ lazylabel_gui-1.3.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
+ lazylabel_gui-1.3.8.dist-info/entry_points.txt,sha256=Hd0WwEG9OPTa_ziYjiD0aRh7R6Fupt-wdQ3sspdc1mM,54
54
+ lazylabel_gui-1.3.8.dist-info/top_level.txt,sha256=YN4uIyrpDBq1wiJaBuZLDipIzyZY0jqJOmmXiPIOUkU,10
55
+ lazylabel_gui-1.3.8.dist-info/RECORD,,
@@ -1,197 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: lazylabel-gui
3
- Version: 1.3.6
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 Applications**
64
-
65
- LazyLabel integrates Meta's Segment Anything Model (SAM) with advanced editing capabilities to enable efficient, high-precision image annotation. Designed for computer vision research and machine learning dataset preparation.
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
- ### Installation
76
- ```bash
77
- pip install lazylabel-gui
78
- lazylabel-gui
79
- ```
80
-
81
- ### Optional: SAM 2.1 Support
82
- For enhanced performance with SAM 2.1 models:
83
- ```bash
84
- pip install git+https://github.com/facebookresearch/sam2.git
85
- ```
86
- **Note:** SAM 2.1 is optional - LazyLabel downloads SAM 1.0 models by default.
87
-
88
- ### Usage
89
- 1. **Open Folder** - Select your image directory
90
- 2. **AI Segmentation** - Click on objects for automatic mask generation
91
- 3. **Manual Refinement** - Edit polygons, merge segments, adjust classifications
92
- 4. **Export** - Generate `.npz` files with one-hot encoded masks for ML training
93
-
94
- ---
95
-
96
- ## Key Features
97
-
98
- - **One-click AI segmentation** with Meta's SAM and SAM 2.1 models
99
- - **Manual polygon drawing** with full vertex control
100
- - **Smart editing tools** - merge segments, adjust class names, and class order
101
- - **ML-ready exports** - One-hot encoded `.npz` format and `.json` for YOLO format
102
- - **Image enhancement** - brightness, contrast, gamma adjustment
103
- - **Advanced image viewer** - zoom, pan, and real-time adjustments
104
- - **Edge cropping** - define custom crop areas to focus on specific regions
105
- - **Undo/Redo system** - full history of all actions
106
- - **Auto-saving** - automatic saving of labels when navigating between images
107
- - **Advanced filtering** - FFT thresholding and color channel thresholding
108
- - **Customizable hotkeys** for all functions
109
-
110
- ---
111
-
112
- ## Essential Hotkeys
113
-
114
- | Action | Key | Description |
115
- |--------|-----|-------------|
116
- | **AI Mode** | `1` | Point-click segmentation |
117
- | **Draw Mode** | `2` | Manual polygon drawing |
118
- | **Edit Mode** | `E` | Select and modify shapes |
119
- | **Save Segment** | `Space` | Confirm current mask |
120
- | **Merge** | `M` | Combine selected segments |
121
- | **Pan** | `Q` + drag | Navigate large images |
122
- | **Positive Point** | `Left Click` | Add to segment |
123
- | **Negative Point** | `Right Click` | Remove from segment |
124
-
125
- **Note:** All hotkeys are fully customizable - Click "Hotkeys" button to personalize your workflow.
126
-
127
- ---
128
-
129
- ## Output Format
130
-
131
- LazyLabel exports data in standardized formats optimized for machine learning workflows:
132
-
133
- ```python
134
- import numpy as np
135
-
136
- # Load your labeled data
137
- data = np.load('your_image.npz')
138
- mask = data['mask'] # Shape: (height, width, num_classes)
139
-
140
- # Each channel is a binary mask for one class
141
- class_0_mask = mask[:, :, 0] # Background
142
- class_1_mask = mask[:, :, 1] # Object type 1
143
- class_2_mask = mask[:, :, 2] # Object type 2
144
- ```
145
-
146
-
147
- **Ideal for:**
148
- - Semantic segmentation datasets
149
- - Instance segmentation training
150
- - Computer vision research
151
- - Automated annotation pipelines
152
-
153
- ---
154
-
155
- ## Development
156
-
157
- **Requirements:** Python 3.10+, ~2.5GB disk space for SAM models (auto-downloaded)
158
-
159
- ### Installation from Source
160
- ```bash
161
- git clone https://github.com/dnzckn/LazyLabel.git
162
- cd LazyLabel
163
- pip install -e .
164
- lazylabel-gui
165
- ```
166
-
167
- ### Testing & Quality
168
- ```bash
169
- # Run test suite (272 tests)
170
- pytest --tb=short
171
-
172
- # Code quality checks
173
- ruff check --fix src/
174
- ```
175
-
176
- ### Architecture
177
- - **Modular design** with clean component separation
178
- - **Signal-based communication** between UI elements
179
- - **Extensible model system** for SAM 1.0 and SAM 2.1 variants
180
- - **Comprehensive test suite** (272 tests with extensive coverage)
181
- - **Multi-view support** for simultaneous image processing
182
-
183
- ---
184
-
185
- ## Contributing
186
-
187
- LazyLabel welcomes contributions! Please review:
188
- - [Usage Manual](src/lazylabel/USAGE_MANUAL.md) for comprehensive user documentation
189
- - [Architecture Guide](src/lazylabel/ARCHITECTURE.md) for technical implementation details
190
- - [Issues page](https://github.com/dnzckn/LazyLabel/issues) for feature requests and bug reports
191
-
192
- ---
193
-
194
- ## Acknowledgments
195
-
196
- - [LabelMe](https://github.com/wkentaro/labelme)
197
- - [Segment-Anything-UI](https://github.com/branislavhesko/segment-anything-ui)