labelimgplusplus 3.2.0__tar.gz → 3.3.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.
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/HISTORY.rst +50 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/PKG-INFO +15 -3
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/README.rst +14 -2
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/labelImgPlusPlus.py +132 -65
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/labelimgplusplus.egg-info/PKG-INFO +15 -3
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/labelimgplusplus.egg-info/SOURCES.txt +2 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/__init__.py +1 -1
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/commands.py +19 -1
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/sam_controller.py +15 -11
- labelimgplusplus-3.3.0/libs/core/sam_types.py +21 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/integrations/mask_to_polygon.py +42 -9
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/resources.py +172 -96
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/utils/constants.py +1 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/__init__.py +2 -1
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/canvas.py +77 -5
- labelimgplusplus-3.3.0/libs/widgets/inlineClassPicker.py +171 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/workspacePages.py +53 -2
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/pyproject.toml +1 -1
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/resources.qrc +1 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/setup.cfg +1 -1
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/CONTRIBUTING.rst +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/LICENSE +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/MANIFEST.in +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/data/predefined_classes.txt +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/labelimgplusplus/__init__.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/labelimgplusplus/plugins.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/labelimgplusplus.egg-info/dependency_links.txt +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/labelimgplusplus.egg-info/entry_points.txt +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/labelimgplusplus.egg-info/requires.txt +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/labelimgplusplus.egg-info/top_level.txt +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/__init__.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/annotation_catalog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/dataset.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/image_pipeline.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/keypoint_config.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/plugin_discovery.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/plugin_manager.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/plugin_registry.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/profiling.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/save_pipeline.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/settings.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/shape.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/shortcut_config.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/task_coordinator.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/ultralytics_export.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/video_decoder.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/video_export.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/video_model.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/video_project.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/video_session.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/video_tracking.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/video_types.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/core/workspace_settings.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/data/predefined_classes.txt +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/__init__.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/annotation_loader.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/annotation_paths.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/annotation_probe.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/coco_io.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/create_ml_io.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/format_metadata.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/labelFile.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/pascal_voc_io.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/yolo_io.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/formats/yolo_seg_io.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/integrations/__init__.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/integrations/image_convert.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/integrations/model_cache.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/integrations/segmentation.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/tools/__init__.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/tools/batch_verify.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/tools/dataset_splitter.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/tools/label_checker.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/utils/__init__.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/utils/dpi.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/utils/hashableQListWidgetItem.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/utils/stringBundle.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/utils/styles.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/utils/ustr.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/utils/utils.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/annotationInspector.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/batchVerifyDialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/colorDialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/combobox.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/commandBar.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/default_label_combobox.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/galleryWidget.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/gallery_status_controller.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/keypointPanel.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/labelCheckerDialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/labelDialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/lightWidget.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/pluginManagerDialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/sam_settings_dialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/shortcutsDialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/splitDialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/statsWidget.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/stats_controller.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/toolBar.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/toolRail.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/ultralyticsExportDialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/videoExportDialog.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/videoTimelineWidget.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/view_scaling.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/workspaceInspector.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/libs/widgets/zoomWidget.py +0 -0
- {labelimgplusplus-3.2.0 → labelimgplusplus-3.3.0}/resources/icons/feather/LICENSE.txt +0 -0
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
History
|
|
2
2
|
=======
|
|
3
3
|
|
|
4
|
+
3.3.0 (2026-08-12)
|
|
5
|
+
------------------
|
|
6
|
+
|
|
7
|
+
Faster-annotation release adding non-modal class confirmation and selectable
|
|
8
|
+
Smart Select geometry while preserving the 3.2 workspace, annotation formats,
|
|
9
|
+
filenames, command-line entry points, plugin APIs, shortcut identifiers, and
|
|
10
|
+
Python 3.8 through 3.13 support.
|
|
11
|
+
|
|
12
|
+
Inline Class Confirmation
|
|
13
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
14
|
+
|
|
15
|
+
* Keep completed Box, Polygon, Smart Select, and video geometry in a transient
|
|
16
|
+
provisional layer until its class is confirmed. Provisional geometry is
|
|
17
|
+
excluded from canonical canvas shapes, the Objects inspector, dirty state,
|
|
18
|
+
saves, plugin snapshots, and undo history.
|
|
19
|
+
* Open a non-modal, screen-clamped class picker beside new geometry with
|
|
20
|
+
filtering, arrow-key navigation, valid new-class entry, Enter confirmation,
|
|
21
|
+
and Escape cancellation. Confirmation preserves the active tool and canvas
|
|
22
|
+
focus so repeated annotation remains uninterrupted.
|
|
23
|
+
* Commit confirmed image or video geometry through one ``CreateShapeCommand``.
|
|
24
|
+
Video confirmation creates an accepted manual anchor; cancellation leaves
|
|
25
|
+
both the in-memory model and durable project unchanged.
|
|
26
|
+
* Retain session-only last-class memory. Default-label mode bypasses the picker,
|
|
27
|
+
while single-class mode bypasses it only after the first class is confirmed
|
|
28
|
+
in the current session.
|
|
29
|
+
|
|
30
|
+
Smart Select Output Modes
|
|
31
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
32
|
+
|
|
33
|
+
* Add the frozen plain-data ``SamResult(polygon, bounds)`` contract. SAM workers
|
|
34
|
+
return a simplified polygon and tight, non-zero, half-open bounds for the
|
|
35
|
+
same largest mask component without mutating Qt or document state.
|
|
36
|
+
* Add a contextual Box/Polygon selector that appears only while Smart Select is
|
|
37
|
+
active. Its validated ``sam/outputMode`` setting defaults to ``polygon`` and
|
|
38
|
+
persists independently of model settings.
|
|
39
|
+
* Route both output types through the provisional picker and the same image and
|
|
40
|
+
video undo path. Existing stale-generation and cancellation guards remain in
|
|
41
|
+
place, and ONNX Runtime, NumPy, OpenCV, and model loading stay lazy.
|
|
42
|
+
|
|
43
|
+
Qualification
|
|
44
|
+
~~~~~~~~~~~~~
|
|
45
|
+
|
|
46
|
+
* Cover image and video confirmation, cancellation, focus, filtering, repeated
|
|
47
|
+
annotation, bypass modes, output bounds, simplification, persistence, stale
|
|
48
|
+
SAM results, manual video anchors, and one-step undo.
|
|
49
|
+
* Qualify the picker and Smart Select selector in light and dark themes at 1x
|
|
50
|
+
and true 2x DPI using 1366x768 and 1440x900 logical approval sizes.
|
|
51
|
+
* Make queued Gallery and annotation-catalog refreshes inert after window
|
|
52
|
+
shutdown so late Qt callbacks cannot submit work to a stopped coordinator.
|
|
53
|
+
|
|
4
54
|
3.2.0 (2026-08-11)
|
|
5
55
|
------------------
|
|
6
56
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: labelimgplusplus
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: labelImg++ is an enhanced graphical image annotation tool with Gallery Mode for browsing and labeling images
|
|
5
5
|
Author-email: Abhik Sarkar <abhiksark@gmail.com>
|
|
6
6
|
License: Copyright (c) <2015-Present> Tzutalin
|
|
@@ -78,7 +78,7 @@ polygons, and keypoints, designed for machine learning and computer vision
|
|
|
78
78
|
projects. It is forked from the original LabelImg with significant
|
|
79
79
|
enhancements.
|
|
80
80
|
|
|
81
|
-
**Version 3.
|
|
81
|
+
**Version 3.3.0 (stable).** Install with
|
|
82
82
|
``pip install labelimgplusplus``.
|
|
83
83
|
|
|
84
84
|
.. image:: https://raw.githubusercontent.com/abhiksark/labelImg-plus-plus/c7fbd5fc08a561206b210706143a50023c82a782/resources/demo/demo.gif
|
|
@@ -132,6 +132,18 @@ Workflow and Interface
|
|
|
132
132
|
annotation-status filter close at hand, and the inspector can be collapsed
|
|
133
133
|
when maximum canvas space is needed.
|
|
134
134
|
|
|
135
|
+
**Faster Class Confirmation**
|
|
136
|
+
New boxes, polygons, Smart Select results, and video geometry remain
|
|
137
|
+
provisional while a non-modal class picker opens beside the annotation.
|
|
138
|
+
Filter or enter a class, press **Enter** to commit one undoable annotation,
|
|
139
|
+
or press **Escape** to discard it without changing the document. Default
|
|
140
|
+
labels and established single-class sessions bypass the picker.
|
|
141
|
+
|
|
142
|
+
**Smart Select Output Modes**
|
|
143
|
+
While Smart Select is active, choose **Box** or **Polygon** from the compact
|
|
144
|
+
canvas control. The choice persists between sessions, and either result
|
|
145
|
+
follows the same provisional class-confirmation and undo workflow.
|
|
146
|
+
|
|
135
147
|
**Direct Ultralytics Dataset Export**
|
|
136
148
|
Choose **Tools → Export Ultralytics Dataset…** to create a ready-to-train
|
|
137
149
|
YOLO detection dataset with ``images/{train,val,test}``, matching
|
|
@@ -401,7 +413,7 @@ Or use **Menu > File > Reset All**
|
|
|
401
413
|
Release History
|
|
402
414
|
---------------
|
|
403
415
|
|
|
404
|
-
This source tree identifies itself as **3.
|
|
416
|
+
This source tree identifies itself as **3.3.0**, the stable 3.3 release. See
|
|
405
417
|
the `release history
|
|
406
418
|
<https://github.com/abhiksark/labelImg-plus-plus/blob/master/HISTORY.rst>`__ for
|
|
407
419
|
version-by-version changes and `GitHub Releases
|
|
@@ -26,7 +26,7 @@ polygons, and keypoints, designed for machine learning and computer vision
|
|
|
26
26
|
projects. It is forked from the original LabelImg with significant
|
|
27
27
|
enhancements.
|
|
28
28
|
|
|
29
|
-
**Version 3.
|
|
29
|
+
**Version 3.3.0 (stable).** Install with
|
|
30
30
|
``pip install labelimgplusplus``.
|
|
31
31
|
|
|
32
32
|
.. image:: https://raw.githubusercontent.com/abhiksark/labelImg-plus-plus/c7fbd5fc08a561206b210706143a50023c82a782/resources/demo/demo.gif
|
|
@@ -80,6 +80,18 @@ Workflow and Interface
|
|
|
80
80
|
annotation-status filter close at hand, and the inspector can be collapsed
|
|
81
81
|
when maximum canvas space is needed.
|
|
82
82
|
|
|
83
|
+
**Faster Class Confirmation**
|
|
84
|
+
New boxes, polygons, Smart Select results, and video geometry remain
|
|
85
|
+
provisional while a non-modal class picker opens beside the annotation.
|
|
86
|
+
Filter or enter a class, press **Enter** to commit one undoable annotation,
|
|
87
|
+
or press **Escape** to discard it without changing the document. Default
|
|
88
|
+
labels and established single-class sessions bypass the picker.
|
|
89
|
+
|
|
90
|
+
**Smart Select Output Modes**
|
|
91
|
+
While Smart Select is active, choose **Box** or **Polygon** from the compact
|
|
92
|
+
canvas control. The choice persists between sessions, and either result
|
|
93
|
+
follows the same provisional class-confirmation and undo workflow.
|
|
94
|
+
|
|
83
95
|
**Direct Ultralytics Dataset Export**
|
|
84
96
|
Choose **Tools → Export Ultralytics Dataset…** to create a ready-to-train
|
|
85
97
|
YOLO detection dataset with ``images/{train,val,test}``, matching
|
|
@@ -349,7 +361,7 @@ Or use **Menu > File > Reset All**
|
|
|
349
361
|
Release History
|
|
350
362
|
---------------
|
|
351
363
|
|
|
352
|
-
This source tree identifies itself as **3.
|
|
364
|
+
This source tree identifies itself as **3.3.0**, the stable 3.3 release. See
|
|
353
365
|
the `release history
|
|
354
366
|
<https://github.com/abhiksark/labelImg-plus-plus/blob/master/HISTORY.rst>`__ for
|
|
355
367
|
version-by-version changes and `GitHub Releases
|
|
@@ -71,6 +71,7 @@ from libs.widgets.annotationInspector import (
|
|
|
71
71
|
UnifiedAnnotationView,
|
|
72
72
|
)
|
|
73
73
|
from libs.widgets.workspacePages import WorkspacePages
|
|
74
|
+
from libs.widgets.inlineClassPicker import InlineClassPicker
|
|
74
75
|
|
|
75
76
|
# Core
|
|
76
77
|
from libs.core.shape import Shape, ShapeType, DEFAULT_LINE_COLOR, DEFAULT_FILL_COLOR
|
|
@@ -85,6 +86,7 @@ from libs.core.workspace_settings import (
|
|
|
85
86
|
clamp_inspector_width, load_workspace_settings,
|
|
86
87
|
)
|
|
87
88
|
from libs.core.sam_controller import SamController
|
|
89
|
+
from libs.core.sam_types import normalize_sam_output_mode
|
|
88
90
|
from libs.core.annotation_catalog import AnnotationCatalog
|
|
89
91
|
from libs.core.dataset import DatasetSnapshot
|
|
90
92
|
from libs.core.image_pipeline import FrameCache, load_image_result
|
|
@@ -144,7 +146,7 @@ from libs.utils.constants import (
|
|
|
144
146
|
SETTING_WIN_POSE, SETTING_WIN_SIZE,
|
|
145
147
|
FORMAT_PASCALVOC, FORMAT_YOLO, FORMAT_CREATEML,
|
|
146
148
|
FORMAT_COCO, FORMAT_YOLO_SEG,
|
|
147
|
-
SETTING_SAM_ENCODER, SETTING_SAM_DECODER,
|
|
149
|
+
SETTING_SAM_ENCODER, SETTING_SAM_DECODER, SETTING_SAM_OUTPUT_MODE,
|
|
148
150
|
)
|
|
149
151
|
from libs.utils.utils import (
|
|
150
152
|
new_icon, themed_icon, new_action, add_actions, format_shortcut, Struct,
|
|
@@ -203,6 +205,8 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
203
205
|
self.settings.load()
|
|
204
206
|
settings = self.settings
|
|
205
207
|
self.workspace_settings = load_workspace_settings(settings)
|
|
208
|
+
self.sam_output_mode = normalize_sam_output_mode(
|
|
209
|
+
settings.get(SETTING_SAM_OUTPUT_MODE, 'polygon'))
|
|
206
210
|
|
|
207
211
|
self.shortcut_config = ShortcutConfig()
|
|
208
212
|
if settings.get(SETTING_SHORTCUTS):
|
|
@@ -317,6 +321,8 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
317
321
|
self.label_dialog = LabelDialog(parent=self, list_item=self.label_hist)
|
|
318
322
|
|
|
319
323
|
self.prev_label_text = ''
|
|
324
|
+
self._session_last_class = None
|
|
325
|
+
self._pending_provisional_shape = None
|
|
320
326
|
|
|
321
327
|
list_layout = QVBoxLayout()
|
|
322
328
|
list_layout.setContentsMargins(0, 0, 0, 0)
|
|
@@ -445,6 +451,9 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
445
451
|
self.canvas.scrollRequest.connect(self.scroll_request)
|
|
446
452
|
|
|
447
453
|
self.canvas.newShape.connect(self.new_shape)
|
|
454
|
+
self.class_picker = InlineClassPicker(self)
|
|
455
|
+
self.class_picker.accepted.connect(self._commit_provisional_shape)
|
|
456
|
+
self.class_picker.cancelled.connect(self._cancel_provisional_shape)
|
|
448
457
|
self.sam_controller = SamController(self)
|
|
449
458
|
self.canvas.samClicked.connect(self.sam_controller.segment_at)
|
|
450
459
|
self._sam_available = segmentation.sam_available()
|
|
@@ -624,7 +633,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
624
633
|
self.toggle_sam_mode,
|
|
625
634
|
self.shortcut_config.get('sam_mode'),
|
|
626
635
|
'tool-smart-select',
|
|
627
|
-
'Click an object to auto-generate
|
|
636
|
+
'Click an object to auto-generate the selected geometry '
|
|
628
637
|
'(requires: pip install labelimgplusplus[sam])',
|
|
629
638
|
enabled=False)
|
|
630
639
|
sam_settings_action = action(
|
|
@@ -1124,6 +1133,9 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
1124
1133
|
self.label_zoom, self.label_coordinates),
|
|
1125
1134
|
self.actions, self.zoom_widget, self)
|
|
1126
1135
|
self.workspace_pages = canvas_column
|
|
1136
|
+
self.workspace_pages.sam_output_toggle.set_mode(self.sam_output_mode)
|
|
1137
|
+
self.workspace_pages.sam_output_toggle.modeChanged.connect(
|
|
1138
|
+
self._set_sam_output_mode)
|
|
1127
1139
|
self.workspace_pages.empty_page.recentActivated.connect(
|
|
1128
1140
|
self._open_workspace_recent)
|
|
1129
1141
|
for tool_action in (
|
|
@@ -1640,6 +1652,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
1640
1652
|
self._update_save_status_style(saved)
|
|
1641
1653
|
|
|
1642
1654
|
def reset_state(self):
|
|
1655
|
+
self._dismiss_class_picker()
|
|
1643
1656
|
self._plugin_document_ready = False
|
|
1644
1657
|
self._restart_workers_if_needed()
|
|
1645
1658
|
self._close_video_decoder()
|
|
@@ -2040,8 +2053,19 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
2040
2053
|
}
|
|
2041
2054
|
active = mapping.get(self.canvas.mode, self.actions.editMode)
|
|
2042
2055
|
active.setChecked(True)
|
|
2056
|
+
if hasattr(self, 'workspace_pages'):
|
|
2057
|
+
self.workspace_pages.sam_output_toggle.setVisible(
|
|
2058
|
+
self.canvas.mode == self.canvas.CREATE_SAM)
|
|
2043
2059
|
self._update_active_tool_status()
|
|
2044
2060
|
|
|
2061
|
+
def _set_sam_output_mode(self, mode):
|
|
2062
|
+
"""Persist the contextual Smart Select geometry choice."""
|
|
2063
|
+
self.sam_output_mode = normalize_sam_output_mode(mode)
|
|
2064
|
+
self.workspace_pages.sam_output_toggle.set_mode(self.sam_output_mode)
|
|
2065
|
+
self.settings[SETTING_SAM_OUTPUT_MODE] = self.sam_output_mode
|
|
2066
|
+
self.settings.save()
|
|
2067
|
+
self._restore_canvas_focus()
|
|
2068
|
+
|
|
2045
2069
|
def _update_active_tool_status(self):
|
|
2046
2070
|
if not hasattr(self, 'label_active_tool'):
|
|
2047
2071
|
return
|
|
@@ -2163,14 +2187,11 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
2163
2187
|
self.pause_video()
|
|
2164
2188
|
editable = self._video_editable()
|
|
2165
2189
|
self.actions.editMode.setEnabled(not drawing and editable)
|
|
2190
|
+
self.actions.create.setEnabled(not drawing and editable)
|
|
2166
2191
|
self.actions.create_polygon.setEnabled(not drawing and editable)
|
|
2167
|
-
if not drawing
|
|
2168
|
-
# Cancel creation.
|
|
2169
|
-
print('Cancel creation.')
|
|
2170
|
-
self.canvas.set_editing(True)
|
|
2192
|
+
if not drawing:
|
|
2171
2193
|
self.canvas.restore_cursor()
|
|
2172
|
-
self.
|
|
2173
|
-
self.actions.create_polygon.setEnabled(editable)
|
|
2194
|
+
self._sync_tool_actions()
|
|
2174
2195
|
|
|
2175
2196
|
def toggle_draw_mode(self, edit=True):
|
|
2176
2197
|
if (self.document_kind == DocumentKind.VIDEO
|
|
@@ -2352,13 +2373,17 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
2352
2373
|
return None
|
|
2353
2374
|
|
|
2354
2375
|
def _ensure_annotation_catalog(self):
|
|
2376
|
+
coordinator = getattr(self, 'task_coordinator', None)
|
|
2377
|
+
if coordinator is None or coordinator.is_shutting_down:
|
|
2378
|
+
return False
|
|
2355
2379
|
snapshot = getattr(self, 'dataset_snapshot', None)
|
|
2356
2380
|
if snapshot is None or not snapshot.image_paths:
|
|
2357
|
-
return
|
|
2381
|
+
return False
|
|
2358
2382
|
if (self.annotation_catalog.snapshot is not snapshot
|
|
2359
2383
|
or (not self.annotation_catalog.entries
|
|
2360
2384
|
and self.annotation_catalog._handle is None)):
|
|
2361
2385
|
self.annotation_catalog.start(snapshot)
|
|
2386
|
+
return True
|
|
2362
2387
|
|
|
2363
2388
|
def _on_catalog_batch(self, statuses):
|
|
2364
2389
|
converted = {
|
|
@@ -2873,69 +2898,105 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
2873
2898
|
|
|
2874
2899
|
# Callback functions:
|
|
2875
2900
|
def new_shape(self):
|
|
2876
|
-
"""
|
|
2877
|
-
|
|
2878
|
-
position MUST be in global coordinates.
|
|
2879
|
-
"""
|
|
2901
|
+
"""Stage completed geometry until its class is confirmed."""
|
|
2880
2902
|
if (self.document_kind == DocumentKind.VIDEO
|
|
2881
2903
|
and not self._ensure_video_editable()):
|
|
2882
|
-
self.
|
|
2883
|
-
return
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2904
|
+
self._cancel_provisional_shape()
|
|
2905
|
+
return
|
|
2906
|
+
shape = self.canvas.provisional_shape
|
|
2907
|
+
if shape is None:
|
|
2908
|
+
return
|
|
2909
|
+
if (self._pending_provisional_shape is not None
|
|
2910
|
+
and self._pending_provisional_shape is not shape):
|
|
2911
|
+
self._dismiss_class_picker(discard=False)
|
|
2912
|
+
self._pending_provisional_shape = shape
|
|
2913
|
+
|
|
2914
|
+
if self.use_default_label_checkbox.isChecked():
|
|
2915
|
+
text = getattr(self, 'default_label', '')
|
|
2916
|
+
if text:
|
|
2917
|
+
self._commit_provisional_shape(text)
|
|
2894
2918
|
else:
|
|
2895
|
-
|
|
2896
|
-
|
|
2919
|
+
self._cancel_provisional_shape()
|
|
2920
|
+
return
|
|
2921
|
+
if self.single_class_mode.isChecked() and self._session_last_class:
|
|
2922
|
+
self._commit_provisional_shape(self._session_last_class)
|
|
2923
|
+
return
|
|
2924
|
+
|
|
2925
|
+
self.class_picker.open_at(
|
|
2926
|
+
self.label_hist,
|
|
2927
|
+
self._session_last_class or self.prev_label_text,
|
|
2928
|
+
self._provisional_picker_anchor(shape))
|
|
2929
|
+
|
|
2930
|
+
def _provisional_picker_anchor(self, shape):
|
|
2931
|
+
"""Return a global logical-pixel anchor beside provisional geometry."""
|
|
2932
|
+
xs = [point.x() for point in shape.points]
|
|
2933
|
+
ys = [point.y() for point in shape.points]
|
|
2934
|
+
offset = self.canvas.offset_to_center()
|
|
2935
|
+
point = QPoint(
|
|
2936
|
+
int(round((max(xs) + offset.x()) * self.canvas.scale)),
|
|
2937
|
+
int(round((min(ys) + offset.y()) * self.canvas.scale)))
|
|
2938
|
+
return self.canvas.mapToGlobal(point)
|
|
2939
|
+
|
|
2940
|
+
def _commit_provisional_shape(self, text):
|
|
2941
|
+
text = str(text).strip()
|
|
2942
|
+
pending = self._pending_provisional_shape
|
|
2943
|
+
if (not text or pending is None
|
|
2944
|
+
or self.canvas.provisional_shape is not pending):
|
|
2945
|
+
self._cancel_provisional_shape()
|
|
2946
|
+
return
|
|
2947
|
+
|
|
2948
|
+
video_before = (self.video_model.snapshot_state()
|
|
2949
|
+
if self.document_kind == DocumentKind.VIDEO else None)
|
|
2950
|
+
shape = self.canvas.commit_provisional_shape(
|
|
2951
|
+
text, generate_color_by_text(text))
|
|
2952
|
+
self._pending_provisional_shape = None
|
|
2953
|
+
if shape is None:
|
|
2954
|
+
return
|
|
2955
|
+
self.add_label(shape)
|
|
2956
|
+
|
|
2957
|
+
if self.document_kind == DocumentKind.VIDEO:
|
|
2958
|
+
track_id = self._store_video_shape_as_manual(shape)
|
|
2959
|
+
self._selected_video_track_id = track_id
|
|
2960
|
+
video_after = self.video_model.snapshot_state()
|
|
2961
|
+
self.undo_stack.push(CreateShapeCommand(
|
|
2962
|
+
self, shape, video_before=video_before,
|
|
2963
|
+
video_after=video_after))
|
|
2964
|
+
self._on_video_model_mutation()
|
|
2965
|
+
self._materialize_video_frame(self.current_video_frame_ref.pts)
|
|
2897
2966
|
else:
|
|
2898
|
-
|
|
2967
|
+
self.undo_stack.push(CreateShapeCommand(self, shape))
|
|
2968
|
+
self.set_dirty()
|
|
2899
2969
|
|
|
2900
|
-
|
|
2901
|
-
self.
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2970
|
+
self.prev_label_text = text
|
|
2971
|
+
self.lastLabel = text
|
|
2972
|
+
self._session_last_class = text
|
|
2973
|
+
if text not in self.label_hist:
|
|
2974
|
+
self.label_hist.append(text)
|
|
2975
|
+
self._update_current_image_stats()
|
|
2976
|
+
self._sync_tool_actions()
|
|
2977
|
+
self._restore_canvas_focus()
|
|
2907
2978
|
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
self.undo_stack.push(VideoModelCommand(
|
|
2914
|
-
self, before, after, 'Create video track'))
|
|
2915
|
-
self._on_video_model_mutation()
|
|
2916
|
-
self._materialize_video_frame(
|
|
2917
|
-
self.current_video_frame_ref.pts)
|
|
2918
|
-
else:
|
|
2919
|
-
# Shape already exists, so record without re-executing it.
|
|
2920
|
-
cmd = CreateShapeCommand(self, shape)
|
|
2921
|
-
self.undo_stack.push(cmd)
|
|
2979
|
+
def _cancel_provisional_shape(self):
|
|
2980
|
+
self._pending_provisional_shape = None
|
|
2981
|
+
self.canvas.discard_provisional_shape()
|
|
2982
|
+
self._sync_tool_actions()
|
|
2983
|
+
self._restore_canvas_focus()
|
|
2922
2984
|
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
self.actions.editMode.setEnabled(True)
|
|
2929
|
-
self.actions.create_polygon.setEnabled(True)
|
|
2930
|
-
if self.document_kind != DocumentKind.VIDEO:
|
|
2931
|
-
self.set_dirty()
|
|
2932
|
-
self._update_current_image_stats()
|
|
2985
|
+
def _restore_canvas_focus(self):
|
|
2986
|
+
QApplication.setActiveWindow(self)
|
|
2987
|
+
self.activateWindow()
|
|
2988
|
+
self.setFocusProxy(self.canvas)
|
|
2989
|
+
self.canvas.setFocus(Qt.OtherFocusReason)
|
|
2933
2990
|
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2991
|
+
def _dismiss_class_picker(self, discard=True):
|
|
2992
|
+
picker = getattr(self, 'class_picker', None)
|
|
2993
|
+
if picker is not None:
|
|
2994
|
+
blocked = picker.blockSignals(True)
|
|
2995
|
+
picker.hide()
|
|
2996
|
+
picker.blockSignals(blocked)
|
|
2997
|
+
self._pending_provisional_shape = None
|
|
2998
|
+
if discard and hasattr(self, 'canvas'):
|
|
2999
|
+
self.canvas.discard_provisional_shape()
|
|
2939
3000
|
|
|
2940
3001
|
def scroll_request(self, delta, orientation):
|
|
2941
3002
|
units = - delta / (8 * 15)
|
|
@@ -4886,6 +4947,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
4886
4947
|
settings[SETTING_GRID_SIZE] = self.canvas._grid_size if self.canvas else 32
|
|
4887
4948
|
settings[SETTING_EDGE_ALIGNMENT] = self.edge_alignment_option.isChecked()
|
|
4888
4949
|
settings[SETTING_SHORTCUTS] = self.shortcut_config.to_dict()
|
|
4950
|
+
settings[SETTING_SAM_OUTPUT_MODE] = self.sam_output_mode
|
|
4889
4951
|
settings.save()
|
|
4890
4952
|
self._shutdown_workers()
|
|
4891
4953
|
|
|
@@ -4896,6 +4958,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
4896
4958
|
QTimer.singleShot(0, self.close)
|
|
4897
4959
|
|
|
4898
4960
|
def _shutdown_workers(self):
|
|
4961
|
+
self._dismiss_class_picker()
|
|
4899
4962
|
self._plugin_document_ready = False
|
|
4900
4963
|
self._publish_plugin_document(new_generation=True, force=True)
|
|
4901
4964
|
if hasattr(self, 'plugin_manager'):
|
|
@@ -6527,7 +6590,11 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
6527
6590
|
# Statistics methods (Issue #19) - Stats shown in gallery mode
|
|
6528
6591
|
def _refresh_all_statistics(self):
|
|
6529
6592
|
"""Complete label data in the shared catalog, then aggregate it."""
|
|
6530
|
-
|
|
6593
|
+
# A refresh may already be queued when closeEvent shuts the worker
|
|
6594
|
+
# lanes down. Submitting from that late Qt callback raises inside a
|
|
6595
|
+
# slot and aborts the process, so closed windows must be inert.
|
|
6596
|
+
if not self._ensure_annotation_catalog():
|
|
6597
|
+
return
|
|
6531
6598
|
self.annotation_catalog.request_statistics()
|
|
6532
6599
|
|
|
6533
6600
|
def _update_current_image_stats(self):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: labelimgplusplus
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: labelImg++ is an enhanced graphical image annotation tool with Gallery Mode for browsing and labeling images
|
|
5
5
|
Author-email: Abhik Sarkar <abhiksark@gmail.com>
|
|
6
6
|
License: Copyright (c) <2015-Present> Tzutalin
|
|
@@ -78,7 +78,7 @@ polygons, and keypoints, designed for machine learning and computer vision
|
|
|
78
78
|
projects. It is forked from the original LabelImg with significant
|
|
79
79
|
enhancements.
|
|
80
80
|
|
|
81
|
-
**Version 3.
|
|
81
|
+
**Version 3.3.0 (stable).** Install with
|
|
82
82
|
``pip install labelimgplusplus``.
|
|
83
83
|
|
|
84
84
|
.. image:: https://raw.githubusercontent.com/abhiksark/labelImg-plus-plus/c7fbd5fc08a561206b210706143a50023c82a782/resources/demo/demo.gif
|
|
@@ -132,6 +132,18 @@ Workflow and Interface
|
|
|
132
132
|
annotation-status filter close at hand, and the inspector can be collapsed
|
|
133
133
|
when maximum canvas space is needed.
|
|
134
134
|
|
|
135
|
+
**Faster Class Confirmation**
|
|
136
|
+
New boxes, polygons, Smart Select results, and video geometry remain
|
|
137
|
+
provisional while a non-modal class picker opens beside the annotation.
|
|
138
|
+
Filter or enter a class, press **Enter** to commit one undoable annotation,
|
|
139
|
+
or press **Escape** to discard it without changing the document. Default
|
|
140
|
+
labels and established single-class sessions bypass the picker.
|
|
141
|
+
|
|
142
|
+
**Smart Select Output Modes**
|
|
143
|
+
While Smart Select is active, choose **Box** or **Polygon** from the compact
|
|
144
|
+
canvas control. The choice persists between sessions, and either result
|
|
145
|
+
follows the same provisional class-confirmation and undo workflow.
|
|
146
|
+
|
|
135
147
|
**Direct Ultralytics Dataset Export**
|
|
136
148
|
Choose **Tools → Export Ultralytics Dataset…** to create a ready-to-train
|
|
137
149
|
YOLO detection dataset with ``images/{train,val,test}``, matching
|
|
@@ -401,7 +413,7 @@ Or use **Menu > File > Reset All**
|
|
|
401
413
|
Release History
|
|
402
414
|
---------------
|
|
403
415
|
|
|
404
|
-
This source tree identifies itself as **3.
|
|
416
|
+
This source tree identifies itself as **3.3.0**, the stable 3.3 release. See
|
|
405
417
|
the `release history
|
|
406
418
|
<https://github.com/abhiksark/labelImg-plus-plus/blob/master/HISTORY.rst>`__ for
|
|
407
419
|
version-by-version changes and `GitHub Releases
|
|
@@ -29,6 +29,7 @@ libs/core/plugin_manager.py
|
|
|
29
29
|
libs/core/plugin_registry.py
|
|
30
30
|
libs/core/profiling.py
|
|
31
31
|
libs/core/sam_controller.py
|
|
32
|
+
libs/core/sam_types.py
|
|
32
33
|
libs/core/save_pipeline.py
|
|
33
34
|
libs/core/settings.py
|
|
34
35
|
libs/core/shape.py
|
|
@@ -82,6 +83,7 @@ libs/widgets/commandBar.py
|
|
|
82
83
|
libs/widgets/default_label_combobox.py
|
|
83
84
|
libs/widgets/galleryWidget.py
|
|
84
85
|
libs/widgets/gallery_status_controller.py
|
|
86
|
+
libs/widgets/inlineClassPicker.py
|
|
85
87
|
libs/widgets/keypointPanel.py
|
|
86
88
|
libs/widgets/labelCheckerDialog.py
|
|
87
89
|
libs/widgets/labelDialog.py
|
|
@@ -75,7 +75,8 @@ class CreateShapeCommand(Command):
|
|
|
75
75
|
Undo removes the shape from canvas and label list.
|
|
76
76
|
"""
|
|
77
77
|
|
|
78
|
-
def __init__(self, main_window, shape
|
|
78
|
+
def __init__(self, main_window, shape, video_before=None,
|
|
79
|
+
video_after=None):
|
|
79
80
|
"""Initialize with reference to main window and the created shape.
|
|
80
81
|
|
|
81
82
|
Args:
|
|
@@ -84,9 +85,21 @@ class CreateShapeCommand(Command):
|
|
|
84
85
|
"""
|
|
85
86
|
self.main_window = main_window
|
|
86
87
|
self.shape = shape
|
|
88
|
+
self.video_before = video_before
|
|
89
|
+
self.video_after = video_after
|
|
90
|
+
|
|
91
|
+
def _restore_video(self, state):
|
|
92
|
+
self.main_window.video_model.restore_state(state)
|
|
93
|
+
self.main_window._on_video_model_mutation()
|
|
94
|
+
frame_ref = self.main_window.current_video_frame_ref
|
|
95
|
+
if frame_ref is not None:
|
|
96
|
+
self.main_window._materialize_video_frame(frame_ref.pts)
|
|
87
97
|
|
|
88
98
|
def execute(self):
|
|
89
99
|
"""Add the shape to canvas and label list."""
|
|
100
|
+
if self.video_after is not None:
|
|
101
|
+
self._restore_video(self.video_after)
|
|
102
|
+
return
|
|
90
103
|
self.main_window.canvas.shapes.append(self.shape)
|
|
91
104
|
_rebuild_spatial(self.main_window.canvas)
|
|
92
105
|
self.main_window.add_label(self.shape)
|
|
@@ -94,6 +107,9 @@ class CreateShapeCommand(Command):
|
|
|
94
107
|
|
|
95
108
|
def undo(self):
|
|
96
109
|
"""Remove the shape from canvas and label list."""
|
|
110
|
+
if self.video_before is not None:
|
|
111
|
+
self._restore_video(self.video_before)
|
|
112
|
+
return
|
|
97
113
|
if self.shape in self.main_window.canvas.shapes:
|
|
98
114
|
self.main_window.canvas.shapes.remove(self.shape)
|
|
99
115
|
_rebuild_spatial(self.main_window.canvas)
|
|
@@ -104,6 +120,8 @@ class CreateShapeCommand(Command):
|
|
|
104
120
|
|
|
105
121
|
@property
|
|
106
122
|
def description(self):
|
|
123
|
+
if self.video_after is not None:
|
|
124
|
+
return f"Create video track '{self.shape.label}'"
|
|
107
125
|
return f"Create shape '{self.shape.label}'"
|
|
108
126
|
|
|
109
127
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# libs/core/sam_controller.py
|
|
2
|
-
"""Orchestrates SAM-assisted polygon creation.
|
|
2
|
+
"""Orchestrates SAM-assisted polygon or rectangle creation.
|
|
3
3
|
|
|
4
4
|
Bridges canvas clicks to worker-thread inference and routes results back to the
|
|
5
5
|
canvas on the main thread. Top-level imports are Qt + stdlib only; numpy and the
|
|
@@ -19,7 +19,7 @@ from libs.core.profiling import recorder as trace_recorder
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class _SamSignals(QObject):
|
|
22
|
-
# (generation,
|
|
22
|
+
# (generation, SamResult or None, backend created this run or None)
|
|
23
23
|
finished = pyqtSignal(int, object, object)
|
|
24
24
|
failed = pyqtSignal(int, str) # (generation, message)
|
|
25
25
|
|
|
@@ -41,15 +41,15 @@ class _SamTask(QRunnable):
|
|
|
41
41
|
|
|
42
42
|
def run(self):
|
|
43
43
|
try:
|
|
44
|
-
generation,
|
|
44
|
+
generation, result, created = self.execute()
|
|
45
45
|
if self._signals is not None:
|
|
46
|
-
self._signals.finished.emit(generation,
|
|
46
|
+
self._signals.finished.emit(generation, result, created)
|
|
47
47
|
except Exception as exc:
|
|
48
48
|
if self._signals is not None:
|
|
49
49
|
self._signals.failed.emit(self._generation, str(exc))
|
|
50
50
|
|
|
51
51
|
def execute(self):
|
|
52
|
-
from libs.integrations.mask_to_polygon import
|
|
52
|
+
from libs.integrations.mask_to_polygon import mask_to_sam_result
|
|
53
53
|
backend = self._backend
|
|
54
54
|
created = None
|
|
55
55
|
if backend is None:
|
|
@@ -72,16 +72,16 @@ class _SamTask(QRunnable):
|
|
|
72
72
|
if trace_recorder is not None:
|
|
73
73
|
trace_recorder.complete('sam.image.embed', started)
|
|
74
74
|
if self._point is None:
|
|
75
|
-
return self._generation,
|
|
75
|
+
return self._generation, None, created
|
|
76
76
|
if trace_recorder is not None:
|
|
77
77
|
import time
|
|
78
78
|
started = time.perf_counter_ns()
|
|
79
79
|
mask = backend.predict(
|
|
80
80
|
[(self._point.x(), self._point.y())], [1])
|
|
81
|
-
|
|
81
|
+
result = mask_to_sam_result(mask)
|
|
82
82
|
if trace_recorder is not None:
|
|
83
83
|
trace_recorder.complete('sam.inference', started)
|
|
84
|
-
return self._generation,
|
|
84
|
+
return self._generation, result, created
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
class SamController:
|
|
@@ -197,7 +197,7 @@ class SamController:
|
|
|
197
197
|
self._embedded_key = None
|
|
198
198
|
self._start_pending()
|
|
199
199
|
|
|
200
|
-
def _on_finished(self, generation,
|
|
200
|
+
def _on_finished(self, generation, result, created):
|
|
201
201
|
# Only one task is ever in flight (segment_at's _busy guard, and neither
|
|
202
202
|
# cancel nor on_image_changed releases _busy), so the task completing here
|
|
203
203
|
# is always the sole in-flight one: clearing _busy is unconditional/safe.
|
|
@@ -209,12 +209,16 @@ class SamController:
|
|
|
209
209
|
if generation != self._gen:
|
|
210
210
|
self._start_pending()
|
|
211
211
|
return
|
|
212
|
-
if
|
|
212
|
+
if result is None:
|
|
213
213
|
if self._active_point is not None:
|
|
214
214
|
self.mw.status("No object found, try another point")
|
|
215
215
|
self._start_pending()
|
|
216
216
|
return
|
|
217
|
-
self.mw
|
|
217
|
+
output_mode = getattr(self.mw, 'sam_output_mode', 'polygon')
|
|
218
|
+
if output_mode == 'box':
|
|
219
|
+
self.mw.canvas.commit_rectangle(result.bounds)
|
|
220
|
+
else:
|
|
221
|
+
self.mw.canvas.commit_polygon(result.polygon)
|
|
218
222
|
self._start_pending()
|
|
219
223
|
|
|
220
224
|
def _on_failed(self, generation, message):
|