PyImageLabeling 1.0.0__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.
Files changed (99) hide show
  1. PyImageLabeling/__init__.py +22 -0
  2. PyImageLabeling/config.json +289 -0
  3. PyImageLabeling/controller/Controller.py +25 -0
  4. PyImageLabeling/controller/Events.py +147 -0
  5. PyImageLabeling/controller/FileEvents.py +69 -0
  6. PyImageLabeling/controller/ImageEvents.py +32 -0
  7. PyImageLabeling/controller/LabelEvents.py +219 -0
  8. PyImageLabeling/controller/LabelingEvents.py +123 -0
  9. PyImageLabeling/controller/settings/ContourFillinSetting.py +93 -0
  10. PyImageLabeling/controller/settings/CoutourFillingApplyCancel.py +37 -0
  11. PyImageLabeling/controller/settings/EraserSetting.py +73 -0
  12. PyImageLabeling/controller/settings/LabelSetting.py +91 -0
  13. PyImageLabeling/controller/settings/MagicPenSetting.py +125 -0
  14. PyImageLabeling/controller/settings/OpacitySetting.py +66 -0
  15. PyImageLabeling/controller/settings/PaintBrushSetting.py +66 -0
  16. PyImageLabeling/icons/apply.png +0 -0
  17. PyImageLabeling/icons/asterisk-green.png +0 -0
  18. PyImageLabeling/icons/asterisk-red.png +0 -0
  19. PyImageLabeling/icons/back.png +0 -0
  20. PyImageLabeling/icons/border.png +0 -0
  21. PyImageLabeling/icons/cancel.png +0 -0
  22. PyImageLabeling/icons/cleaner.png +0 -0
  23. PyImageLabeling/icons/close.png +0 -0
  24. PyImageLabeling/icons/down.png +0 -0
  25. PyImageLabeling/icons/ellipse.png +0 -0
  26. PyImageLabeling/icons/eraser.png +0 -0
  27. PyImageLabeling/icons/filling.png +0 -0
  28. PyImageLabeling/icons/logoMAIA.png +0 -0
  29. PyImageLabeling/icons/magic.png +0 -0
  30. PyImageLabeling/icons/maia.png +0 -0
  31. PyImageLabeling/icons/maia1.png +0 -0
  32. PyImageLabeling/icons/maia3.ico +0 -0
  33. PyImageLabeling/icons/maia_icon.png +0 -0
  34. PyImageLabeling/icons/move.png +0 -0
  35. PyImageLabeling/icons/opacity.png +0 -0
  36. PyImageLabeling/icons/open_image.png +0 -0
  37. PyImageLabeling/icons/open_layer.png +0 -0
  38. PyImageLabeling/icons/paint.png +0 -0
  39. PyImageLabeling/icons/plus.png +0 -0
  40. PyImageLabeling/icons/polygon.png +0 -0
  41. PyImageLabeling/icons/rectangle.png +0 -0
  42. PyImageLabeling/icons/reset.png +0 -0
  43. PyImageLabeling/icons/save.png +0 -0
  44. PyImageLabeling/icons/setting.png +0 -0
  45. PyImageLabeling/icons/transparency.png:Zone.Identifier +4 -0
  46. PyImageLabeling/icons/up.png +0 -0
  47. PyImageLabeling/icons/visibility.png +0 -0
  48. PyImageLabeling/icons/zoom_minus.png +0 -0
  49. PyImageLabeling/icons/zoom_plus.png +0 -0
  50. PyImageLabeling/model/Core.py +795 -0
  51. PyImageLabeling/model/File/Files.py +166 -0
  52. PyImageLabeling/model/File/NextImage.py +36 -0
  53. PyImageLabeling/model/File/PreviousImage.py +19 -0
  54. PyImageLabeling/model/Image/MoveImage.py +32 -0
  55. PyImageLabeling/model/Image/ResetMoveZoomImage.py +16 -0
  56. PyImageLabeling/model/Image/ZoomMinus.py +25 -0
  57. PyImageLabeling/model/Image/ZoomPlus.py +16 -0
  58. PyImageLabeling/model/Labeling/ClearAll.py +22 -0
  59. PyImageLabeling/model/Labeling/ContourFilling.py +135 -0
  60. PyImageLabeling/model/Labeling/Ellipse.py +350 -0
  61. PyImageLabeling/model/Labeling/Eraser.py +131 -0
  62. PyImageLabeling/model/Labeling/MagicPen.py +131 -0
  63. PyImageLabeling/model/Labeling/PaintBrush.py +207 -0
  64. PyImageLabeling/model/Labeling/Polygon.py +279 -0
  65. PyImageLabeling/model/Labeling/Rectangle.py +248 -0
  66. PyImageLabeling/model/Labeling/Undo.py +12 -0
  67. PyImageLabeling/model/Model.py +40 -0
  68. PyImageLabeling/model/Utils.py +40 -0
  69. PyImageLabeling/old_version/label_rectangle_properties.json +6 -0
  70. PyImageLabeling/old_version/main.py +2073 -0
  71. PyImageLabeling/old_version/models/EraseSettingsDialog.py +51 -0
  72. PyImageLabeling/old_version/models/LabeledRectangle.py +80 -0
  73. PyImageLabeling/old_version/models/MagicSettingsDialog.py +119 -0
  74. PyImageLabeling/old_version/models/OverlayOpacityDialog.py +63 -0
  75. PyImageLabeling/old_version/models/PaintSettingsDialog.py +289 -0
  76. PyImageLabeling/old_version/models/PointItem.py +66 -0
  77. PyImageLabeling/old_version/models/ProcessWorker.py +52 -0
  78. PyImageLabeling/old_version/models/ZoomableGraphicsView.py +1214 -0
  79. PyImageLabeling/old_version/models/tools/ContourTool.py +279 -0
  80. PyImageLabeling/old_version/models/tools/EraserTool.py +290 -0
  81. PyImageLabeling/old_version/models/tools/MagicPenTool.py +199 -0
  82. PyImageLabeling/old_version/models/tools/OverlayTool.py +179 -0
  83. PyImageLabeling/old_version/models/tools/PaintTool.py +68 -0
  84. PyImageLabeling/old_version/models/tools/PolygonTool.py +786 -0
  85. PyImageLabeling/old_version/models/tools/RectangleTool.py +1036 -0
  86. PyImageLabeling/parameters.json +1 -0
  87. PyImageLabeling/style.css +611 -0
  88. PyImageLabeling/view/Builder.py +333 -0
  89. PyImageLabeling/view/QBackgroundItem.py +30 -0
  90. PyImageLabeling/view/QWidgets.py +10 -0
  91. PyImageLabeling/view/View.py +226 -0
  92. PyImageLabeling/view/ZoomableGraphicsView.py +91 -0
  93. PyImageLabeling/view/__init__.py +0 -0
  94. pyimagelabeling-1.0.0.dist-info/METADATA +55 -0
  95. pyimagelabeling-1.0.0.dist-info/RECORD +99 -0
  96. pyimagelabeling-1.0.0.dist-info/WHEEL +5 -0
  97. pyimagelabeling-1.0.0.dist-info/licenses/LICENCE +22 -0
  98. pyimagelabeling-1.0.0.dist-info/top_level.txt +2 -0
  99. pypi/publish_pypi.py +18 -0
@@ -0,0 +1,51 @@
1
+ import cv2
2
+ import numpy as np
3
+ import sys
4
+ import os
5
+ import time
6
+ from PyQt6.QtWidgets import (
7
+ QGraphicsEllipseItem, QComboBox, QGraphicsRectItem, QInputDialog, QGraphicsItem, QGraphicsItemGroup, QGraphicsPixmapItem, QGraphicsOpacityEffect, QGraphicsView, QGraphicsScene, QApplication, QMainWindow, QLabel, QVBoxLayout, QPushButton,
8
+ QFileDialog, QWidget, QMessageBox, QHBoxLayout, QColorDialog, QDialog, QSlider, QFormLayout, QDialogButtonBox, QGridLayout, QProgressDialog, QCheckBox, QSpinBox, QSplashScreen, QMenu
9
+ )
10
+ from PyQt6.QtGui import QPixmap, QMouseEvent, QImage, QPainter, QColor, QPen, QBrush, QCursor, QIcon, QPainterPath, QFont
11
+ from PyQt6.QtCore import Qt, QPoint, QPointF, QTimer, QThread, pyqtSignal, QSize, QRectF, QObject, QLineF
12
+ import gc
13
+ import math
14
+ import traceback
15
+
16
+ class EraseSettingsDialog(QDialog):
17
+ def __init__(self, parent=None, current_eraser_size=None, absolute_mode=False):
18
+ super().__init__(parent)
19
+ self.setWindowTitle("Eraser Settings")
20
+ self.eraser_size = current_eraser_size or 10
21
+ self.absolute_mode = absolute_mode
22
+
23
+ layout = QFormLayout()
24
+
25
+ # Eraser size slider
26
+ self.eraser_slider = QSlider(Qt.Orientation.Horizontal)
27
+ self.eraser_slider.setRange(1, 100)
28
+ self.eraser_slider.setValue(self.eraser_size)
29
+ layout.addRow("Eraser Size:", self.eraser_slider)
30
+ self.eraser_spinbox = QSpinBox()
31
+
32
+ self.eraser_spinbox.setRange(1, 100)
33
+ self.eraser_spinbox.setValue(self.eraser_size)
34
+ self.eraser_spinbox.valueChanged.connect(self.eraser_slider.setValue)
35
+ self.eraser_slider.valueChanged.connect(self.eraser_spinbox.setValue)
36
+ layout.addRow("", self.eraser_spinbox)
37
+
38
+ # Add absolute mode checkbox
39
+ self.absolute_checkbox = QCheckBox("Absolute Mode")
40
+ self.absolute_checkbox.setChecked(self.absolute_mode)
41
+ layout.addRow("", self.absolute_checkbox)
42
+
43
+ self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
44
+ self.buttons.accepted.connect(self.accept)
45
+ self.buttons.rejected.connect(self.reject)
46
+ layout.addRow(self.buttons)
47
+
48
+ self.setLayout(layout)
49
+
50
+ def get_settings(self):
51
+ return self.eraser_slider.value(), self.absolute_checkbox.isChecked()
@@ -0,0 +1,80 @@
1
+
2
+ import cv2
3
+ import numpy as np
4
+ import sys
5
+ import os
6
+ import time
7
+ from PyQt6.QtWidgets import (
8
+ QGraphicsEllipseItem, QComboBox, QGraphicsRectItem, QInputDialog, QGraphicsItem, QGraphicsItemGroup, QGraphicsPixmapItem, QGraphicsOpacityEffect, QGraphicsView, QGraphicsScene, QApplication, QMainWindow, QLabel, QVBoxLayout, QPushButton,
9
+ QFileDialog, QWidget, QMessageBox, QHBoxLayout, QColorDialog, QDialog, QSlider, QFormLayout, QDialogButtonBox, QGridLayout, QProgressDialog, QCheckBox, QSpinBox, QSplashScreen, QMenu, QGraphicsTextItem
10
+ )
11
+ from PyQt6.QtGui import QPixmap, QMouseEvent, QImage, QPainter, QColor, QPen, QBrush, QCursor, QIcon, QPainterPath, QFont
12
+ from PyQt6.QtCore import Qt, QPoint, QPointF, QTimer, QThread, pyqtSignal, QSize, QRectF, QObject, QLineF
13
+ import gc
14
+ import math
15
+ import traceback
16
+
17
+ class LabeledRectangle(QGraphicsRectItem):
18
+ def __init__(self, x, y, width, height, label="", color = None):
19
+ super().__init__(x, y, width, height)
20
+ self.label = label
21
+ self.color = color if color else QColor(255, 0, 0) # Red outline
22
+
23
+ self.setPen(QPen(QColor(self.color), 2))
24
+
25
+ # Create label text item
26
+ self.text_item = QGraphicsTextItem(self.label, self)
27
+ self.text_item.setDefaultTextColor(QColor(255, 255, 255))
28
+ self.text_item.setFont(QFont("Arial", 10, QFont.Weight.Bold))
29
+
30
+ # Position the text at the top-left corner of the rectangle
31
+ self.text_item.setPos(x, y - 20) # Slightly above the rectangle
32
+
33
+ # Make the rectangle selectable and movable
34
+ self.setFlag(QGraphicsRectItem.GraphicsItemFlag.ItemIsSelectable, True)
35
+ self.setFlag(QGraphicsRectItem.GraphicsItemFlag.ItemIsMovable, True)
36
+
37
+ def set_color(self, color):
38
+ """Update the rectangle color"""
39
+ self.color = color
40
+ self.setPen(QPen(QColor(self.color), 2))
41
+
42
+ def set_label(self, label):
43
+ """Update the rectangle label"""
44
+ self.label = label
45
+ if hasattr(self, 'text_item'):
46
+ self.text_item.setPlainText(label)
47
+
48
+ def set_thickness(self, thickness):
49
+ """Update the rectangle thickness"""
50
+ self.thickness = thickness
51
+ self.setPen(QPen(QColor(self.color), self.thickness))
52
+
53
+ def get_color(self):
54
+ """Get the current color"""
55
+ return self.color
56
+
57
+ def get_label(self):
58
+ """Get the current label"""
59
+ return self.label
60
+
61
+ def boundingRect(self):
62
+ """Return the bounding rectangle including the text"""
63
+ rect = super().boundingRect()
64
+ if hasattr(self, 'text_item'):
65
+ text_rect = self.text_item.boundingRect()
66
+ text_rect.translate(self.text_item.pos())
67
+ rect = rect.united(text_rect)
68
+ return rect
69
+
70
+ def contains(self, point):
71
+ """Check if point is within the rectangle (for click detection)"""
72
+ return super().contains(point)
73
+
74
+ def itemChange(self, change, value):
75
+ """Handle item changes (like position changes)"""
76
+ if change == QGraphicsRectItem.GraphicsItemChange.ItemPositionChange and hasattr(self, 'text_item'):
77
+ # Update text position when rectangle moves
78
+ new_pos = value
79
+ self.text_item.setPos(new_pos.x(), new_pos.y() - 20)
80
+ return super().itemChange(change, value)
@@ -0,0 +1,119 @@
1
+ import cv2
2
+ import numpy as np
3
+ import sys
4
+ import os
5
+ import time
6
+ from PyQt6.QtWidgets import (
7
+ QGraphicsEllipseItem, QComboBox, QGraphicsRectItem, QInputDialog, QGraphicsItem, QGraphicsItemGroup, QGraphicsPixmapItem, QGraphicsOpacityEffect, QGraphicsView, QGraphicsScene, QApplication, QMainWindow, QLabel, QVBoxLayout, QPushButton,
8
+ QFileDialog, QWidget, QMessageBox, QHBoxLayout, QColorDialog, QDialog, QSlider, QFormLayout, QDialogButtonBox, QGridLayout, QProgressDialog, QCheckBox, QSpinBox, QSplashScreen, QMenu
9
+ )
10
+ from PyQt6.QtGui import QPixmap, QMouseEvent, QImage, QPainter, QColor, QPen, QBrush, QCursor, QIcon, QPainterPath, QFont
11
+ from PyQt6.QtCore import Qt, QPoint, QPointF, QTimer, QThread, pyqtSignal, QSize, QRectF, QObject, QLineF
12
+ import gc
13
+ import math
14
+ import traceback
15
+
16
+ class MagicSettingsDialog(QDialog):
17
+ def __init__(self, parent=None, current_tolerance=20, current_timeout=5, current_max_points=100000):
18
+ super().__init__(parent)
19
+ self.setWindowTitle("Magic Pen Settings")
20
+ self.tolerance = current_tolerance
21
+ self.max_point = current_max_points
22
+ layout = QVBoxLayout()
23
+
24
+ # Create a form layout for the controls
25
+ form_layout = QFormLayout()
26
+
27
+ # Tolerance slider
28
+ self.tolerance_slider = QSlider(Qt.Orientation.Horizontal)
29
+ self.tolerance_slider.setRange(0, 100)
30
+ self.tolerance_slider.setValue(self.tolerance)
31
+ self.tolerance_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
32
+ self.tolerance_slider.setTickInterval(10)
33
+ form_layout.addRow("Tolerance:", self.tolerance_slider)
34
+
35
+ # Tolerance spinbox
36
+ self.tolerance_spinbox = QSpinBox()
37
+ self.tolerance_spinbox.setRange(0, 100)
38
+ self.tolerance_spinbox.setValue(self.tolerance)
39
+
40
+ # Connect slider and spinbox
41
+ self.tolerance_spinbox.valueChanged.connect(self.tolerance_slider.setValue)
42
+ self.tolerance_slider.valueChanged.connect(self.tolerance_spinbox.setValue)
43
+
44
+ form_layout.addRow("Value:", self.tolerance_spinbox)
45
+
46
+ # Add a description of what different tolerance values mean
47
+ tolerance_help = QLabel("Lower values = more precise color matching\nHigher values = more inclusive fill")
48
+ tolerance_help.setStyleSheet("color: #666; font-style: italic;")
49
+ form_layout.addRow("", tolerance_help)
50
+
51
+ # Add form layout to main layout
52
+ layout.addLayout(form_layout)
53
+
54
+ # Add timeout setting
55
+ timeout_layout = QHBoxLayout()
56
+ timeout_label = QLabel("Timeout (seconds):")
57
+ self.timeout_spinbox = QSpinBox()
58
+ self.timeout_spinbox.setRange(1, 60)
59
+ self.timeout_spinbox.setValue(current_timeout)
60
+ timeout_layout.addWidget(timeout_label)
61
+ timeout_layout.addWidget(self.timeout_spinbox)
62
+ layout.addLayout(timeout_layout)
63
+
64
+ # Add MAX_POINTS_LIMIT setting
65
+ layout.addSpacing(10)
66
+ points_limit_layout = QVBoxLayout()
67
+
68
+ points_limit_label = QLabel("Maximum Points Limit:")
69
+ points_limit_layout.addWidget(points_limit_label)
70
+
71
+ points_slider_layout = QHBoxLayout()
72
+
73
+ # Points limit slider
74
+ self.points_limit_slider = QSlider(Qt.Orientation.Horizontal)
75
+ self.points_limit_slider.setRange(5000, 500000)
76
+ self.points_limit_slider.setValue(self.max_point)
77
+ self.points_limit_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
78
+ self.points_limit_slider.setTickInterval(50000)
79
+
80
+ # Points limit spinbox
81
+ self.points_limit_spinbox = QSpinBox()
82
+ self.points_limit_spinbox.setRange(5000, 500000)
83
+ self.points_limit_spinbox.setValue(self.max_point)
84
+ self.points_limit_spinbox.setSingleStep(5000)
85
+
86
+ # Connect slider and spinbox
87
+ self.points_limit_spinbox.valueChanged.connect(self.points_limit_slider.setValue)
88
+ self.points_limit_slider.valueChanged.connect(self.points_limit_spinbox.setValue)
89
+
90
+ points_slider_layout.addWidget(self.points_limit_slider)
91
+ points_slider_layout.addWidget(self.points_limit_spinbox)
92
+ points_limit_layout.addLayout(points_slider_layout)
93
+
94
+ # Add description for points limit
95
+ points_limit_help = QLabel("Higher values allow filling larger areas but may use more memory")
96
+ points_limit_help.setStyleSheet("color: #666; font-style: italic;")
97
+ points_limit_layout.addWidget(points_limit_help)
98
+
99
+ layout.addLayout(points_limit_layout)
100
+
101
+ # Add spacer
102
+ layout.addSpacing(10)
103
+
104
+ # Add buttons
105
+ self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
106
+ self.buttons.accepted.connect(self.accept)
107
+ self.buttons.rejected.connect(self.reject)
108
+ layout.addWidget(self.buttons)
109
+
110
+ self.setLayout(layout)
111
+
112
+ def get_tolerance(self):
113
+ return self.tolerance_slider.value()
114
+
115
+ def get_timeout(self):
116
+ return self.timeout_spinbox.value()
117
+
118
+ def get_max_points_limit(self):
119
+ return self.points_limit_spinbox.value()
@@ -0,0 +1,63 @@
1
+ import cv2
2
+ import numpy as np
3
+ import sys
4
+ import os
5
+ import time
6
+ from PyQt6.QtWidgets import (
7
+ QGraphicsEllipseItem, QComboBox, QGraphicsRectItem, QInputDialog, QGraphicsItem, QGraphicsItemGroup, QGraphicsPixmapItem, QGraphicsOpacityEffect, QGraphicsView, QGraphicsScene, QApplication, QMainWindow, QLabel, QVBoxLayout, QPushButton,
8
+ QFileDialog, QWidget, QMessageBox, QHBoxLayout, QColorDialog, QDialog, QSlider, QFormLayout, QDialogButtonBox, QGridLayout, QProgressDialog, QCheckBox, QSpinBox, QSplashScreen, QMenu
9
+ )
10
+ from PyQt6.QtGui import QPixmap, QMouseEvent, QImage, QPainter, QColor, QPen, QBrush, QCursor, QIcon, QPainterPath, QFont
11
+ from PyQt6.QtCore import Qt, QPoint, QPointF, QTimer, QThread, pyqtSignal, QSize, QRectF, QObject, QLineF
12
+ import gc
13
+ import math
14
+ import traceback
15
+
16
+ class OverlayOpacityDialog(QDialog):
17
+ def __init__(self, parent=None, current_opacity=255):
18
+ super().__init__(parent)
19
+ self.setWindowTitle("Overlay Opacity")
20
+
21
+ layout = QFormLayout()
22
+
23
+ # Opacity slider
24
+ self.slider = QSlider(Qt.Orientation.Horizontal)
25
+ self.slider.setRange(0, 255)
26
+ self.slider.setValue(current_opacity)
27
+ self.slider.setTickPosition(QSlider.TickPosition.TicksBelow)
28
+ self.slider.setTickInterval(25)
29
+ layout.addRow("Opacity:", self.slider)
30
+
31
+ # Opacity spinbox
32
+ self.opacity_spinbox = QSpinBox()
33
+ self.opacity_spinbox.setRange(0, 255)
34
+ self.opacity_spinbox.setValue(current_opacity)
35
+ layout.addRow("", self.opacity_spinbox)
36
+
37
+ # Sync slider and spinbox
38
+ self.opacity_spinbox.valueChanged.connect(self.slider.setValue)
39
+ self.slider.valueChanged.connect(self.opacity_spinbox.setValue)
40
+
41
+ # Preset buttons layout
42
+ preset_layout = QHBoxLayout()
43
+ presets = [(25, "25%"), (50, "50%"), (75, "75%"), (100, "100%")]
44
+ for value, label in presets:
45
+ btn = QPushButton(label)
46
+ btn.setFixedHeight(25)
47
+ btn.setProperty("class", "preset")
48
+ btn.clicked.connect(lambda checked, v=value: self.set_preset(v))
49
+ preset_layout.addWidget(btn)
50
+ layout.addRow("", preset_layout)
51
+
52
+ # Buttons
53
+ self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
54
+ self.buttons.accepted.connect(self.accept)
55
+ self.buttons.rejected.connect(self.reject)
56
+ layout.addRow(self.buttons)
57
+
58
+ self.setLayout(layout)
59
+
60
+ def set_preset(self, percentage):
61
+ """Set opacity to a preset percentage value"""
62
+ value = int((percentage / 100) * 255)
63
+ self.slider.setValue(value)
@@ -0,0 +1,289 @@
1
+ import cv2
2
+ import numpy as np
3
+ import sys
4
+ import os
5
+ import time
6
+ from PyQt6.QtWidgets import (
7
+ QGraphicsEllipseItem, QComboBox, QGraphicsRectItem, QInputDialog, QGraphicsItem, QGraphicsItemGroup, QGraphicsPixmapItem, QGraphicsOpacityEffect, QGraphicsView, QGraphicsScene, QApplication, QMainWindow, QLabel, QVBoxLayout, QPushButton,
8
+ QFileDialog, QWidget, QMessageBox, QHBoxLayout, QColorDialog, QDialog, QSlider, QFormLayout, QDialogButtonBox, QGridLayout, QProgressDialog, QCheckBox, QSpinBox, QSplashScreen, QMenu, QLineEdit
9
+ )
10
+ from PyQt6.QtGui import QPixmap, QMouseEvent, QImage, QPainter, QColor, QPen, QBrush, QCursor, QIcon, QPainterPath, QFont
11
+ from PyQt6.QtCore import Qt, QPoint, QPointF, QTimer, QThread, pyqtSignal, QSize, QRectF, QObject, QLineF
12
+ import gc
13
+ import math
14
+ import traceback
15
+ import json
16
+
17
+ class LabelPaintPropertiesDialog(QDialog):
18
+ def __init__(self, parent=None):
19
+ super().__init__(parent)
20
+ self.setWindowTitle("Label Properties")
21
+ self.setMinimumWidth(300)
22
+ self.setStyleSheet("""
23
+ QDialog {
24
+ background-color: #000000;
25
+ color: white;
26
+ border: 1px solid #444444;
27
+ }
28
+ QLabel {
29
+ color: white;
30
+ font-size: 14px;
31
+ }
32
+ """)
33
+
34
+ layout = QVBoxLayout()
35
+
36
+ self.label_name = QLabel("Label: ")
37
+ self.label_color = QLabel("Color: ")
38
+ self.label_radius = QLabel("Radius: ")
39
+ self.label_opacity = QLabel("Opacity: ")
40
+
41
+ layout.addWidget(self.label_name)
42
+ layout.addWidget(self.label_color)
43
+ layout.addWidget(self.label_radius)
44
+ layout.addWidget(self.label_opacity)
45
+
46
+ self.setLayout(layout)
47
+
48
+ def update_properties(self, label, color, radius, opacity):
49
+ self.label_name.setText(f"Label: {label}")
50
+ self.label_color.setText(f"Color: {color.name()}")
51
+ self.label_radius.setText(f"Radius: {radius}")
52
+ self.label_opacity.setText(f"Opacity: {opacity}")
53
+
54
+ class LabelPropertiesManager:
55
+ """Manages saving and loading of label properties"""
56
+
57
+ def __init__(self, properties_file="label_paint_properties.json"):
58
+ self.properties_file = properties_file
59
+ self.label_properties = {}
60
+ self.load_properties()
61
+
62
+ def save_properties(self):
63
+ """Save label properties to JSON file"""
64
+ try:
65
+ # Convert QColor objects to serializable format
66
+ serializable_props = {}
67
+ for label, props in self.label_properties.items():
68
+ color = props['color']
69
+ if isinstance(color, str):
70
+ color = QColor(color) # Convert string to QColor if needed
71
+ serializable_props[label] = {
72
+ 'color': color.name(), # Safely get hex color
73
+ 'radius': props['radius'],
74
+ 'opacity': props['opacity']
75
+ }
76
+
77
+ with open(self.properties_file, 'w') as f:
78
+ json.dump(serializable_props, f, indent=2)
79
+ return True
80
+ except Exception as e:
81
+ print(f"Error saving label properties: {e}")
82
+ return False
83
+
84
+ def load_properties(self):
85
+ """Load label properties from JSON file"""
86
+ try:
87
+ if os.path.exists(self.properties_file):
88
+ with open(self.properties_file, 'r') as f:
89
+ data = json.load(f)
90
+
91
+ # Convert hex strings back to QColor objects
92
+ for label, props in data.items():
93
+ self.label_properties[label] = {
94
+ 'color': QColor(props['color']),
95
+ 'radius': props['radius'],
96
+ 'opacity': props['opacity']
97
+ }
98
+ return True
99
+ except Exception as e:
100
+ print(f"Error loading label properties: {e}")
101
+ return False
102
+
103
+ def add_label_property(self, label, color, radius, opacity):
104
+ """Add or update a label property"""
105
+ self.label_properties[label] = {
106
+ 'color': color,
107
+ 'radius': radius,
108
+ 'opacity': opacity
109
+ }
110
+ self.save_properties()
111
+
112
+ def get_label_property(self, label):
113
+ """Get properties for a specific label"""
114
+ return self.label_properties.get(label, None)
115
+
116
+ def get_all_labels(self):
117
+ """Get all saved label names"""
118
+ return list(self.label_properties.keys())
119
+
120
+ def remove_label_property(self, label):
121
+ """Remove a label property"""
122
+ if label in self.label_properties:
123
+ del self.label_properties[label]
124
+ self.save_properties()
125
+ return True
126
+ return False
127
+
128
+ class PaintSettingsDialog(QDialog):
129
+ saved_labels = {}
130
+ label_manager = LabelPropertiesManager()
131
+ def __init__(self, parent=None, current_color=None, current_radius=None, current_opacity=None, current_label=None):
132
+ super().__init__(parent)
133
+ self.setWindowTitle("Paint Settings")
134
+
135
+ # Initialize with current values or defaults
136
+ self.color = current_color if current_color else QColor(255, 0, 0)
137
+ self.radius = current_radius if current_radius else 3
138
+ self.opacity = current_opacity if current_opacity else 255
139
+ self.label = current_label if current_label else ""
140
+
141
+ layout = QFormLayout()
142
+
143
+ # Label selection/input - MODIFIED
144
+ label_layout = QHBoxLayout()
145
+
146
+ self.label_combo = QComboBox()
147
+ self.label_combo.setEditable(True)
148
+ self.label_combo.setPlaceholderText("Enter new label or select existing")
149
+
150
+ # Populate combo box with saved labels
151
+ self.label_combo.addItem("") # Empty option for new labels
152
+ for label in self.label_manager.get_all_labels():
153
+ self.label_combo.addItem(label)
154
+
155
+ # Set current label if provided
156
+ if self.label:
157
+ index = self.label_combo.findText(self.label)
158
+ if index >= 0:
159
+ self.label_combo.setCurrentIndex(index)
160
+ else:
161
+ self.label_combo.setCurrentText(self.label)
162
+
163
+ self.label_combo.currentTextChanged.connect(self.on_label_selected)
164
+ label_layout.addWidget(self.label_combo)
165
+
166
+ layout.addRow("Label:", label_layout)
167
+
168
+ # Color selection
169
+ self.color_button = QPushButton("Choose Color")
170
+ self.color_button.clicked.connect(self.choose_color)
171
+ self.update_color_button() # NEW METHOD CALL
172
+ layout.addRow("Color:", self.color_button)
173
+
174
+ # Radius slider
175
+ self.radius_slider = QSlider(Qt.Orientation.Horizontal)
176
+ self.radius_slider.setRange(1, 100)
177
+ self.radius_slider.setValue(self.radius)
178
+ self.radius_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
179
+ self.radius_slider.setTickInterval(10)
180
+ layout.addRow("Radius:", self.radius_slider)
181
+
182
+ self.radius_spinbox = QSpinBox()
183
+ self.radius_spinbox.setRange(1, 100)
184
+ self.radius_spinbox.setValue(self.radius)
185
+ self.radius_spinbox.valueChanged.connect(self.radius_slider.setValue)
186
+ self.radius_slider.valueChanged.connect(self.radius_spinbox.setValue)
187
+ layout.addRow("", self.radius_spinbox)
188
+
189
+ self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
190
+ self.buttons.accepted.connect(self.accept)
191
+ self.buttons.rejected.connect(self.reject)
192
+ layout.addRow(self.buttons)
193
+
194
+ self.setLayout(layout)
195
+
196
+ def on_label_selected(self, text):
197
+ """Handle label selection from combo box"""
198
+ if text:
199
+ label_props = self.label_manager.get_label_property(text)
200
+ if label_props:
201
+ # Load saved settings for this label
202
+ self.color = label_props['color']
203
+ self.radius = label_props['radius']
204
+ self.opacity = label_props['opacity']
205
+
206
+ # Update UI elements
207
+ self.radius_slider.setValue(self.radius)
208
+ self.radius_spinbox.setValue(self.radius)
209
+ self.update_color_button()
210
+
211
+ def update_color_button(self):
212
+ """Update color button appearance to show current color"""
213
+ color_style = f"background-color: rgb({self.color.red()}, {self.color.green()}, {self.color.blue()}); color: {'white' if self.color.lightness() < 128 else 'black'};"
214
+ self.color_button.setStyleSheet(color_style)
215
+ self.color_button.setText(f"Color: {self.color.name()}")
216
+
217
+ def choose_color(self):
218
+ color = QColorDialog.getColor(initial=self.color)
219
+ if color.isValid():
220
+ self.color = color
221
+ self.update_color_button() # NEW METHOD CALL
222
+
223
+ def get_settings(self):
224
+ self.radius = self.radius_slider.value()
225
+ self.label = self.label_combo.currentText().strip()
226
+
227
+ # Save label settings if label is not empty - NEW FUNCTIONALITY
228
+ if self.label:
229
+ self.label_manager.add_label_property(self.label, self.color, self.radius, self.opacity)
230
+
231
+ return self.color, self.radius, self.opacity, self.label
232
+
233
+ def add_overlay(self, overlay_pixmap):
234
+ """Add an overlay pixmap that stays aligned with the base image"""
235
+ if not self.base_pixmap:
236
+ return False
237
+
238
+ # Remove existing overlay if any
239
+ self.remove_overlay()
240
+
241
+ # Ensure overlay matches base image dimensions
242
+ base_size = self.base_pixmap.size()
243
+ if overlay_pixmap.size() != base_size:
244
+ overlay_pixmap = overlay_pixmap.scaled(
245
+ base_size,
246
+ Qt.AspectRatioMode.IgnoreAspectRatio,
247
+ Qt.TransformationMode.SmoothTransformation
248
+ )
249
+
250
+ # Create overlay pixmap item
251
+ self.overlay_pixmap_item = self.scene.addPixmap(overlay_pixmap)
252
+ self.overlay_pixmap_item.setZValue(1) # Above base image
253
+ self.overlay_pixmap_item.setPos(0, 0) # Same position as base image
254
+
255
+ # Set opacity (default 50%)
256
+ if not hasattr(self, 'overlay_opacity'):
257
+ self.overlay_opacity = 128
258
+ self.overlay_pixmap_item.setOpacity(self.overlay_opacity / 255.0)
259
+
260
+ # Update scene
261
+ self.scene.update()
262
+ return True
263
+
264
+ def set_overlay_opacity(self, opacity):
265
+ """Set opacity of the overlay layer (0-255)"""
266
+ if self.overlay_pixmap_item:
267
+ self.overlay_opacity = max(0, min(255, opacity))
268
+ self.overlay_pixmap_item.setOpacity(self.overlay_opacity / 255.0)
269
+ self.scene.update()
270
+ return True
271
+ return False
272
+
273
+ def toggle_overlay_visibility(self):
274
+ """Toggle visibility of the overlay"""
275
+ if self.overlay_pixmap_item:
276
+ is_visible = self.overlay_pixmap_item.isVisible()
277
+ self.overlay_pixmap_item.setVisible(not is_visible)
278
+ self.scene.update()
279
+ return not is_visible
280
+ return False
281
+
282
+ def remove_overlay(self):
283
+ """Remove overlay if exists"""
284
+ if self.overlay_pixmap_item:
285
+ self.scene.removeItem(self.overlay_pixmap_item)
286
+ self.overlay_pixmap_item = None
287
+ self.scene.update()
288
+ return True
289
+ return False
@@ -0,0 +1,66 @@
1
+ import cv2
2
+ import numpy as np
3
+ import sys
4
+ import os
5
+ import time
6
+ from PyQt6.QtWidgets import (
7
+ QGraphicsEllipseItem, QComboBox, QGraphicsRectItem, QInputDialog, QGraphicsItem, QGraphicsItemGroup, QGraphicsPixmapItem, QGraphicsOpacityEffect, QGraphicsView, QGraphicsScene, QApplication, QMainWindow, QLabel, QVBoxLayout, QPushButton,
8
+ QFileDialog, QWidget, QMessageBox, QHBoxLayout, QColorDialog, QDialog, QSlider, QFormLayout, QDialogButtonBox, QGridLayout, QProgressDialog, QCheckBox, QSpinBox, QSplashScreen, QMenu
9
+ )
10
+ from PyQt6.QtGui import QPixmap, QMouseEvent, QImage, QPainter, QColor, QPen, QBrush, QCursor, QIcon, QPainterPath, QFont
11
+ from PyQt6.QtCore import Qt, QPoint, QPointF, QTimer, QThread, pyqtSignal, QSize, QRectF, QObject, QLineF
12
+ import gc
13
+ import math
14
+ import traceback
15
+
16
+ class PointItem(QGraphicsEllipseItem):
17
+ """Custom graphics item for representing a point/dot with fixed size"""
18
+ def __init__(self, label, x, y, radius, color, opacity=100):
19
+ # Store properties
20
+ self.fixed_label = label
21
+ self.fixed_x = x
22
+ self.fixed_y = y
23
+ self._fixed_radius = radius
24
+ self.fixed_color = QColor(color)
25
+ self.fixed_opacity = int(255 * opacity / 100)
26
+
27
+ # Create the ellipse using the fixed radius
28
+ super().__init__(x - radius, y - radius, radius * 2, radius * 2)
29
+
30
+ self.setPen(QPen(Qt.PenStyle.NoPen))
31
+ self.fixed_color.setAlpha(self.fixed_opacity)
32
+ self.setBrush(QBrush(self.fixed_color))
33
+ self.setZValue(10) # Ensure it's drawn above the image
34
+
35
+ # Disable any modifications
36
+ self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
37
+ self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
38
+
39
+ def get_position(self):
40
+ """Always return the original center point"""
41
+ return QPointF(self.fixed_x, self.fixed_y)
42
+
43
+ def boundingRect(self):
44
+ """Always return the original bounding rect with fixed radius"""
45
+ return QRectF(self.fixed_x - self._fixed_radius,
46
+ self.fixed_y - self._fixed_radius,
47
+ self._fixed_radius * 2,
48
+ self._fixed_radius * 2)
49
+
50
+ def shape(self):
51
+ """Ensure the shape remains fixed"""
52
+ path = QPainterPath()
53
+ path.addEllipse(QRectF(self.fixed_x - self._fixed_radius,
54
+ self.fixed_y - self._fixed_radius,
55
+ self._fixed_radius * 2,
56
+ self._fixed_radius * 2))
57
+ return path
58
+
59
+ def paint(self, painter, option, widget=None):
60
+ """Recreate the original ellipse with fixed radius and color"""
61
+ painter.setBrush(QBrush(self.fixed_color))
62
+ painter.setPen(QPen(Qt.PenStyle.NoPen))
63
+ painter.drawEllipse(QRectF(self.fixed_x - self._fixed_radius,
64
+ self.fixed_y - self._fixed_radius,
65
+ self._fixed_radius * 2,
66
+ self._fixed_radius * 2))