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,795 @@
1
+
2
+ from PyQt6.QtGui import QPainter, QBitmap, QImage, QPixmap, QColor, QPainter, QBrush, QPen
3
+ from PyQt6.QtCore import Qt, QSize
4
+ from PyQt6.QtWidgets import QFileDialog
5
+ from PyImageLabeling.view.QBackgroundItem import QBackgroundItem
6
+
7
+ from PIL import Image
8
+ import numpy
9
+ from collections import deque
10
+
11
+ from PyImageLabeling.model.Utils import Utils
12
+
13
+ import os
14
+ import json
15
+
16
+ KEYWORD_SAVE_LABEL = ".label."
17
+
18
+ class LabelingOverlay():
19
+ ###
20
+ # A LabelingOverlay is composed of a QPixmap, a QGraphicItem, a QPainter and a previous QPixmap
21
+ ###
22
+
23
+ def __init__(self, label, scene, width, height, from_file=None):
24
+ self.label = label
25
+
26
+ self.scene = scene # The associated QGraphicScene of the QGraphicsView
27
+ self.width = width # The width of the Labeling Overlay QPixmap
28
+ self.height = height # The height of the Labeling Overlay QPixmap
29
+ self.zvalue = 3 # The default ZValue
30
+ self.opacity = Utils.load_parameters()["labeling_opacity"]/100 # To normalize
31
+
32
+ #self.label_id = label_id
33
+ #self.name = name
34
+ #self.labeling_mode = labeling_mode
35
+ #self.color = QColor(color) # The color of labels in the Labeling Overlay
36
+ # Note: The color is in RGB, the alpha is set at only the end for the view part :)
37
+
38
+
39
+ # Initialize the QPixmap
40
+ self.labeling_overlay_pixmap = QPixmap(QSize(self.width, self.height))
41
+ if from_file is None:
42
+ self.labeling_overlay_pixmap.fill(Qt.GlobalColor.transparent)
43
+ else:
44
+ # Load the the file in the pixmap
45
+ self.labeling_overlay_pixmap.load(from_file)
46
+ pass
47
+ self.labeling_overlay_item = None
48
+
49
+ # Initialize the deque for the `undo` feature
50
+ self.undo_deque = deque()
51
+ self.undo_deque.append(self.labeling_overlay_pixmap.copy())
52
+
53
+ # Initialize the associated QPainter
54
+ self.labeling_overlay_painter = QPainter(self.labeling_overlay_pixmap)
55
+ self.reset_pen()
56
+
57
+ # Initialize the previous pixmap for the `undo` feature
58
+ self.previous_labeling_overlay_pixmap = None
59
+
60
+ # Initialize others pixmaps and painters to change the color or opacity operations
61
+ self.labeling_overlay_opacity_pixmap = QPixmap(self.width, self.height)
62
+ self.labeling_overlay_opacity_painter = QPainter()
63
+
64
+ self.labeling_overlay_color_pixmap = QPixmap(self.width, self.height)
65
+ self.labeling_overlay_color_painter = QPainter()
66
+
67
+ self.is_displayed_in_scene = False
68
+ self.is_edited = False
69
+
70
+ def set_is_edited(self, is_edited):
71
+ self.is_edited = is_edited
72
+
73
+ def get_is_edited(self):
74
+ return self.is_edited
75
+
76
+ def update_scene(self):
77
+ if self.is_displayed_in_scene is False:
78
+ self.labeling_overlay_item = self.scene.addPixmap(self.generate_opacity_pixmap())
79
+ self.labeling_overlay_item.setZValue(self.zvalue)
80
+ self.is_displayed_in_scene = True
81
+
82
+ self.labeling_overlay_item.setVisible(self.label.get_visible())
83
+
84
+ #def change_visible(self):
85
+ # if self.labeling_overlay_item.isVisible() is True:
86
+ # self.labeling_overlay_item.setVisible(False)
87
+ # else:
88
+ # self.labeling_overlay_item.setVisible(True)
89
+
90
+ def reset(self):
91
+ #self.labeling_overlay_painter.end()
92
+
93
+ self.labeling_overlay_pixmap.fill(Qt.GlobalColor.transparent)
94
+ if self.is_displayed_in_scene is True:
95
+ self.labeling_overlay_item.setPixmap(self.labeling_overlay_pixmap)
96
+
97
+ first_labeling_overlay_pixmap = self.undo_deque[0].copy()
98
+ self.undo_deque.clear()
99
+ self.undo_deque.append(first_labeling_overlay_pixmap)
100
+
101
+ self.previous_labeling_overlay_pixmap = None
102
+
103
+ if self.get_is_edited() is False:
104
+ self.set_is_edited(True)
105
+
106
+ def remove(self):
107
+ if self.is_displayed_in_scene is True:
108
+ self.scene.removeItem(self.labeling_overlay_item)
109
+ self.labeling_overlay_item = None
110
+
111
+ self.labeling_overlay_painter.end()
112
+
113
+ def set_opacity(self, opacity):
114
+ self.opacity = opacity
115
+
116
+ # Change and update the QPixmap
117
+ if self.is_displayed_in_scene is True:
118
+ self.labeling_overlay_item.setPixmap(self.generate_opacity_pixmap())
119
+
120
+ def get_opacity(self):
121
+ return self.opacity
122
+
123
+ def undo(self):
124
+ if len(self.undo_deque) > 0:
125
+ self.labeling_overlay_painter.end()
126
+
127
+ self.labeling_overlay_pixmap = self.undo_deque.pop()
128
+ self.previous_labeling_overlay_pixmap = self.labeling_overlay_pixmap.copy()
129
+
130
+ if len(self.undo_deque) == 0:
131
+ self.undo_deque.append(self.labeling_overlay_pixmap.copy())
132
+
133
+ # Create a new pixmap for opacity
134
+
135
+ self.labeling_overlay_item.setPixmap(self.generate_opacity_pixmap())
136
+ self.labeling_overlay_painter.begin(self.labeling_overlay_pixmap)
137
+ self.reset_pen()
138
+
139
+ def generate_opacity_pixmap(self):
140
+ # Now we can fill the pixmap
141
+ self.labeling_overlay_opacity_pixmap.fill(Qt.GlobalColor.transparent)
142
+
143
+ # Restart the painter
144
+ self.labeling_overlay_opacity_painter.begin(self.labeling_overlay_opacity_pixmap)
145
+ self.labeling_overlay_opacity_painter.setOpacity(self.get_opacity())
146
+ self.labeling_overlay_opacity_painter.drawPixmap(0, 0, self.labeling_overlay_pixmap)
147
+ self.labeling_overlay_opacity_painter.end()
148
+
149
+ return self.labeling_overlay_opacity_pixmap
150
+
151
+ def update_color(self):
152
+ # We change this labeling overlay
153
+ if self.get_is_edited() is False:
154
+ self.set_is_edited(True)
155
+
156
+ # Apply Color
157
+ self.labeling_overlay_color_pixmap.fill(self.label.get_color())
158
+ self.labeling_overlay_painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
159
+ self.labeling_overlay_painter.drawPixmap(0, 0, self.labeling_overlay_color_pixmap)
160
+ self.labeling_overlay_painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
161
+
162
+ # Update
163
+ if self.is_displayed_in_scene is True:
164
+ self.labeling_overlay_item.setPixmap(self.generate_opacity_pixmap())
165
+
166
+ # Apply the color on the undo pixmaps
167
+ for pixmap in self.undo_deque:
168
+ self.labeling_overlay_color_painter.begin(pixmap)
169
+ self.labeling_overlay_color_painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
170
+ self.labeling_overlay_color_painter.drawPixmap(0, 0, self.labeling_overlay_color_pixmap)
171
+ self.labeling_overlay_color_painter.end()
172
+
173
+ # do not forget the previous undo pixmap :)
174
+ if self.previous_labeling_overlay_pixmap is not None:
175
+ self.labeling_overlay_color_painter.begin(self.previous_labeling_overlay_pixmap)
176
+ self.labeling_overlay_color_painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
177
+ self.labeling_overlay_color_painter.drawPixmap(0, 0, self.labeling_overlay_color_pixmap)
178
+ self.labeling_overlay_color_painter.end()
179
+
180
+
181
+ def update(self):
182
+ # We change this labeling overlay
183
+ if self.get_is_edited() is False:
184
+ self.set_is_edited(True)
185
+
186
+ # Change and update the QPixmap
187
+ self.labeling_overlay_item.setPixmap(self.generate_opacity_pixmap())
188
+
189
+ # For the `undo` feature, if we have a previous, add it in the deque
190
+ if self.previous_labeling_overlay_pixmap is not None:
191
+ self.undo_deque.append(self.previous_labeling_overlay_pixmap)
192
+
193
+ # Create a copy to keep it in the previous pixmap variable
194
+ self.previous_labeling_overlay_pixmap = self.labeling_overlay_pixmap.copy()
195
+
196
+ #def get_color(self):
197
+ # return self.color
198
+
199
+ #def set_color(self, color):
200
+ # self.color = color
201
+
202
+ #def get_name(self):
203
+ # return self.name
204
+
205
+ # def set_name(self, name):
206
+ # self.name = name
207
+
208
+ # def get_labeling_mode(self):
209
+ # return self.labeling_mode
210
+
211
+ # def set_labeling_mode(self, labeling_mode):
212
+ # self.labeling_mode = labeling_mode
213
+
214
+ # def get_label_id(self):
215
+ # return self.label_id
216
+
217
+ def get_painter(self):
218
+ return self.labeling_overlay_painter
219
+
220
+ def set_zvalue(self, zvalue):
221
+ self.zvalue = zvalue
222
+ print("esss:", zvalue)
223
+ self.labeling_overlay_item.setZValue(self.zvalue)
224
+
225
+ def reset_pen(self):
226
+ self.labeling_overlay_painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
227
+ self.labeling_overlay_painter.setPen(QPen(self.label.color, 2, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin))
228
+ self.labeling_overlay_painter.setBrush(self.label.color)
229
+
230
+ def set_is_displayed_in_scene(self, is_displayed_in_scene):
231
+ self.is_displayed_in_scene = is_displayed_in_scene
232
+
233
+ def set_visible(self, is_visible):
234
+ if self.labeling_overlay_item is not None:
235
+ self.labeling_overlay_item.setVisible(is_visible)
236
+
237
+ def save(self, current_file_path, path_image):
238
+ name = os.path.basename(path_image)
239
+ name, format = name.split(".")
240
+ format = "png"
241
+ save_file = current_file_path + os.sep+name + KEYWORD_SAVE_LABEL + str(self.label.get_label_id()) + "." + format
242
+ self.labeling_overlay_pixmap.save(save_file, format)
243
+
244
+ def remove_save(self, current_file_path, path_image):
245
+ name = os.path.basename(path_image)
246
+ name, format = name.split(".")
247
+ format = "png"
248
+ save_file = current_file_path + os.sep+name + KEYWORD_SAVE_LABEL + str(self.label.get_label_id()) + "." + format
249
+ if os.path.isfile(save_file):
250
+ os.remove(save_file)
251
+
252
+ class ImageItem():
253
+
254
+ def __init__(self, view, controller, path_image, icon_button, labeling_overview_was_loaded, labeling_overview_file_paths):
255
+ self.view = view
256
+ self.controller = controller
257
+ self.path_image = path_image
258
+ self.icon_button = icon_button
259
+ self.labeling_overview_was_loaded = labeling_overview_was_loaded
260
+ self.labeling_overview_file_paths = labeling_overview_file_paths
261
+
262
+ self.zoomable_graphics_view = view.zoomable_graphics_view
263
+
264
+ self.image_pixmap = QPixmap(path_image) # The current pixmap of the image
265
+ self.image_item = None # The current pixmap item of the image in the scene
266
+ self.image_numpy_pixels_rgb = None # The current RGB numpy matrix of the image
267
+
268
+ self.backgroung_item = None # The QBackgroundItem behind the images
269
+
270
+ self.labeling_overlays = dict() # dict of LabelingOverlay instance
271
+
272
+ self.current_labeling_overlay = None # The current selected LabelingOverlay
273
+
274
+ self.image_qrect = self.image_pixmap.rect() # Integer size in Qrect
275
+ self.image_qrectf = self.image_qrect.toRectF() # Float size in QRectF
276
+
277
+ # the background item
278
+ self.alpha_color = Utils.load_parameters()["load"]["alpha_color"]
279
+
280
+ #save a numpy matrix of colors
281
+ self.image_numpy_pixels_rgb = numpy.array(Image.open(path_image).convert("RGB"))
282
+
283
+ self.is_displayed_in_scene = False
284
+
285
+
286
+ def get_edited(self):
287
+ for label_id in self.labeling_overlays:
288
+ if self.labeling_overlays[label_id].get_is_edited() is True:
289
+ return True
290
+ return False
291
+
292
+ def get_image_pixmap(self):
293
+ return self.image_pixmap
294
+
295
+ def get_qrectf(self):
296
+ return self.image_qrectf
297
+
298
+ def get_labeling_overlays(self):
299
+ return list(self.labeling_overlays.values())
300
+
301
+ def update_scene(self):
302
+ if self.is_displayed_in_scene is False:
303
+ # Add the backgroung_item in the scene
304
+ self.backgroung_item = QBackgroundItem(self.image_qrectf, self.controller, self.alpha_color)
305
+ self.view.zoomable_graphics_view.scene.addItem(self.backgroung_item)
306
+ self.backgroung_item.setZValue(0) # Base layer
307
+
308
+ # Add the image in the scene
309
+ self.image_item = self.zoomable_graphics_view.scene.addPixmap(self.image_pixmap)
310
+ self.image_item.setZValue(1) # Image layer
311
+
312
+ # Set the Event listener in the background item to the image item
313
+ self.image_item.installSceneEventFilter(self.backgroung_item)
314
+
315
+ # Set the good visual parameters for the scene
316
+ self.zoomable_graphics_view.setSceneRect(self.image_qrectf)
317
+ self.zoomable_graphics_view.setAlignment(Qt.AlignmentFlag.AlignCenter)
318
+ self.zoomable_graphics_view.centerOn(self.image_qrect.width()/2,self.image_qrect.height()/2)
319
+
320
+ self.initialyse_zoom_factor()
321
+ self.is_displayed_in_scene = True
322
+ # for label_id in self.labeling_overlays:
323
+ # self.labeling_overlays[label_id].update_scene()
324
+
325
+ # # Apply the label's visibility state using existing change_visible logic
326
+ # if label_id in self.controller.model.get_label_items():
327
+ # label_item = self.controller.model.get_label_items()[label_id]
328
+ # overlay = self.labeling_overlays[label_id]
329
+
330
+ # # Only change if current visibility doesn't match desired state
331
+ # if overlay.labeling_overlay_item.isVisible() != label_item.get_visible():
332
+ # overlay.change_visible()
333
+
334
+ # Update the labeling overlays
335
+ for label_id in self.labeling_overlays:
336
+ self.labeling_overlays[label_id].update_scene()
337
+
338
+ print("update scene")
339
+
340
+ def clear_scene(self):
341
+ self.is_displayed_in_scene = False
342
+ for label_id in self.labeling_overlays:
343
+ self.labeling_overlays[label_id].set_is_displayed_in_scene(False)
344
+
345
+ def initialyse_zoom_factor(self):
346
+ self.view.min_zoom = self.view.zoomable_graphics_view.data_parameters["zoom"]["min_zoom"]
347
+ self.view.max_zoom = self.view.zoomable_graphics_view.data_parameters["zoom"]["max_zoom"]
348
+
349
+ viewport_width = self.view.zoomable_graphics_view.viewport().width()
350
+ viewport_height = self.view.zoomable_graphics_view.viewport().height()
351
+ diagonal_viewport = numpy.sqrt(viewport_width**2 + viewport_height**2)
352
+ diagonal_pixmap = numpy.sqrt(self.image_qrect.width()**2 + self.image_qrect.height()**2)
353
+
354
+ self.view.zoom_factor = diagonal_pixmap / diagonal_viewport
355
+ if self.view.zoom_factor < self.view.min_zoom: self.view.min_zoom = self.view.zoom_factor
356
+ self.view.max_zoom = diagonal_pixmap/self.view.max_zoom
357
+ self.view.initial_zoom_factor = self.view.zoom_factor
358
+
359
+ def update_labeling_overlays(self, label_items, selected_label_id):
360
+ # Ensure all existing labels have an overlay
361
+ for label_id in label_items:
362
+ print("label_id:", label_id)
363
+ if label_id not in self.labeling_overlays:
364
+ basename_key = ".".join(os.path.basename(self.path_image).split(".")[:-1])+KEYWORD_SAVE_LABEL+str(label_id)+".png"
365
+ print("basename_key:", basename_key)
366
+ if basename_key in self.labeling_overview_was_loaded and self.labeling_overview_was_loaded[basename_key] is False:
367
+ print("We have to load this labeling overlay")
368
+ self.labeling_overlays[label_id] = LabelingOverlay(
369
+ label_items[label_id],
370
+ self.view.zoomable_graphics_view.scene,
371
+ self.image_qrect.width(),
372
+ self.image_qrect.height(),
373
+ self.labeling_overview_file_paths[basename_key]
374
+ )
375
+ self.labeling_overview_was_loaded[basename_key] = True
376
+ else:
377
+ print("We have not to load this labeling overlay")
378
+ self.labeling_overlays[label_id] = LabelingOverlay(
379
+ label_items[label_id],
380
+ self.view.zoomable_graphics_view.scene,
381
+ self.image_qrect.width(),
382
+ self.image_qrect.height()
383
+ )
384
+ self.current_labeling_overlay = self.labeling_overlays[selected_label_id]
385
+ self.current_label_id = selected_label_id
386
+
387
+ def foreground_current_labeling_overlay(self):
388
+ for label_id in self.labeling_overlays:
389
+ if label_id == self.current_label_id:
390
+ self.labeling_overlays[label_id].set_zvalue(3)
391
+ else:
392
+ self.labeling_overlays[label_id].set_zvalue(2)
393
+ # Force the visibility
394
+ # self.current_labeling_overlay.labeling_overlay_item.setVisible(True)
395
+ # print("update_labeling_overlays end")
396
+
397
+
398
+
399
+ # def new_labeling_overlay(self, label_id, name, labeling_mode, color):
400
+ # # Add a new labeling overlay
401
+ # self.current_labeling_overlay =
402
+
403
+ # # Add the data of this new label in the label dictionnary
404
+ # self.labeling_overlays[label_id] = self.current_labeling_overlay
405
+
406
+ # # Set the current label
407
+ # self.current_label_id = label_id
408
+
409
+ # # Put at the first plan the current label
410
+ # self.foreground_current_labeling_overlay()
411
+
412
+ # # Change the current labeling overlay
413
+ # def select_labeling_overlay(self, label_id):
414
+ # self.current_label_id = label_id
415
+
416
+ # self.current_labeling_overlay = self.labeling_overlays[label_id]
417
+ # self.current_labeling_overlay.labeling_overlay_item.setVisible(True)
418
+ # self.foreground_current_labeling_overlay()
419
+
420
+ def n_labeling_overlays(self):
421
+ return len(self.labeling_overlays)
422
+
423
+ def get_labeling_overlay(self):
424
+ return self.current_labeling_overlay
425
+
426
+ def update_icon_file(self):
427
+ if self.get_edited() is True:
428
+ self.icon_button.setPixmap(self.view.icon_asterisk_red)
429
+ else:
430
+ self.icon_button.setPixmap(self.view.icon_asterisk_green)
431
+
432
+ # Update the current labeling overlay
433
+ def update_labeling_overlay(self):
434
+ self.current_labeling_overlay.update()
435
+ self.update_icon_file()
436
+
437
+ # # Put at the foreground the current labeling overlay
438
+ # def foreground_current_labeling_overlay(self):
439
+ # for label_id in self.labeling_overlays:
440
+ # if label_id == self.current_label_id:
441
+ # self.labeling_overlays[label_id].set_zvalue(3)
442
+ # else:
443
+ # self.labeling_overlays[label_id].set_zvalue(2)
444
+ # self.view.zoomable_graphics_view.scene.update()
445
+
446
+ # Return QPixmap of all labeling overlay except the current one in a ordered list.
447
+ def get_labeling_overlay_pixmaps(self):
448
+ if self.n_labeling_overlays() == 1:
449
+ return []
450
+ result = [labeling_overlay for labeling_overlay in self.labeling_overlays.values() if labeling_overlay != self.current_labeling_overlay]
451
+ result.sort(key=lambda x: x.label.get_label_id())
452
+ return [element.labeling_overlay_pixmap for element in result]
453
+
454
+ def set_opacity(self, opacity):
455
+ for labeling_overlay in self.labeling_overlays.values():
456
+ labeling_overlay.set_opacity(opacity)
457
+
458
+ def update_color(self, label_id):
459
+ self.labeling_overlays[label_id].update_color()
460
+ self.update_icon_file()
461
+
462
+ def get_image_numpy_pixels_rgb(self):
463
+ return self.image_numpy_pixels_rgb
464
+
465
+ def get_width(self):
466
+ return self.image_qrect.width()
467
+
468
+ def get_height(self):
469
+ return self.image_qrect.height()
470
+
471
+ def save_overlays(self, current_file_path):
472
+ for labeling_overlay in self.labeling_overlays.values():
473
+ if labeling_overlay.get_is_edited() is True:
474
+ #img = labeling_overlay.labeling_overlay_pixmap.toImage()
475
+ #if not img.isNull() and not img.allGray():
476
+ #print("save:", current_file_path, self.path_image)
477
+ labeling_overlay.save(current_file_path, self.path_image)
478
+ #else:
479
+ # print("remove:", current_file_path, self.path_image)
480
+ # labeling_overlay.remove_save(current_file_path, self.path_image)
481
+ labeling_overlay.set_is_edited(False)
482
+
483
+ self.update_icon_file()
484
+
485
+ class LabelItem():
486
+
487
+ static_label_id = 0
488
+ used_ids = set()
489
+
490
+ def __init__(self, name, labeling_mode, color, id = None):
491
+ if id is None:
492
+ while LabelItem.static_label_id in LabelItem.used_ids:
493
+ LabelItem.static_label_id += 1
494
+ self.label_id = LabelItem.static_label_id
495
+ LabelItem.used_ids.add(self.label_id)
496
+ LabelItem.static_label_id += 1
497
+ else:
498
+ self.label_id = id
499
+ LabelItem.used_ids.add(self.label_id)
500
+ if id >= LabelItem.static_label_id:
501
+ LabelItem.static_label_id = id + 1
502
+ self.name = name
503
+ self.labeling_mode = labeling_mode
504
+ self.color = QColor(color) # The color of labels in the Labeling Overlay
505
+ self.is_visible = True
506
+
507
+
508
+ def to_dict(self):
509
+ return {"name": self.name, "labeling_mode": self.labeling_mode, "color": [self.color.red(), self.color.green(), self.color.blue()]}
510
+
511
+ def get_label_id(self):
512
+ return self.label_id
513
+
514
+ def get_color(self):
515
+ return self.color
516
+
517
+ def set_color(self, color):
518
+ self.color = color
519
+
520
+ def get_name(self):
521
+ return self.name
522
+
523
+ def set_name(self, name):
524
+ self.name = name
525
+
526
+ def get_labeling_mode(self):
527
+ return self.labeling_mode
528
+
529
+ def set_labeling_mode(self, labeling_mode):
530
+ self.labeling_mode = labeling_mode
531
+
532
+ def set_visible(self, is_visible):
533
+ self.is_visible = is_visible
534
+
535
+ def get_visible(self):
536
+ return self.is_visible
537
+
538
+
539
+ class Core():
540
+
541
+ def __init__(self):
542
+
543
+ #self.labels = dict() # All labels in the form of {'label1': {'name': 'label1', 'color': <PyQt6.QtGui.QColor>, 'labeling_mode': 'Pixel-by-pixel'}, ...}
544
+
545
+ self.checked_button = None # The current button checked => usefull to know the labelinf tool to use
546
+
547
+ self.file_paths = [] # List of file paths of images (in the good order)
548
+
549
+ self.image_items = dict() # Dictionnary: (key: file_path) -> (value: ImageItem or None)
550
+ self.label_items = dict() # Dictionnary: (key: label_id) -> (value: LabelItem)
551
+
552
+ self.current_label_item = None # The current label selected
553
+ self.current_image_item = None # The current ImageItem that is display
554
+
555
+ self.icon_button_files = dict()
556
+
557
+ self.save_directory = ""
558
+
559
+ # For a file_path, say if a labeling overview was loaded or not
560
+ self.labeling_overview_was_loaded = dict() # Dictionnary: (key: basename) -> (value: True or False)
561
+
562
+ # To obtain the file path
563
+ self.labeling_overview_file_paths = dict() # Dictionnary: (key: basename) -> (value: file_path)
564
+
565
+
566
+
567
+ def reset(self):
568
+
569
+ for file in self.file_paths:
570
+ if self.image_items[file] is not None:
571
+ to_delete = []
572
+ for labeling_overlay_key in self.image_items[file].labeling_overlays:
573
+ self.image_items[file].labeling_overlays[labeling_overlay_key].reset()
574
+ self.image_items[file].labeling_overlays[labeling_overlay_key].remove()
575
+ to_delete.append(labeling_overlay_key)
576
+
577
+ for labeling_overlay_key in to_delete:
578
+
579
+ del self.image_items[file].labeling_overlays[labeling_overlay_key]
580
+
581
+
582
+
583
+ def get_edited(self):
584
+ for file in self.file_paths:
585
+ if self.image_items[file] is not None and self.image_items[file].get_edited() is True:
586
+ return True
587
+ return False
588
+
589
+ def get_label_items(self):
590
+ return self.label_items
591
+
592
+ def get_image_items(self):
593
+ return self.image_items
594
+
595
+ def get_current_label_item(self):
596
+ return self.current_label_item
597
+
598
+ def set_current_label_item(self, current_label_item):
599
+ self.current_label_item = current_label_item
600
+
601
+ def get_current_image_item(self):
602
+ return self.current_image_item
603
+
604
+ def get_current_labeling_overlay(self, label_id):
605
+ return self.current_image_item.labeling_overlays[label_id]
606
+
607
+ def get_static_label_id(self):
608
+ return LabelItem.static_label_id
609
+
610
+ def set_view(self, view):
611
+ self.view = view
612
+ self.zoomable_graphics_view = view.zoomable_graphics_view # short-cut
613
+
614
+ def set_controller(self, controller):
615
+ self.controller = controller
616
+
617
+ def set_opacity(self, opacity):
618
+ for file in self.file_paths:
619
+ if self.image_items[file] is not None:
620
+ self.image_items[file].set_opacity(opacity)
621
+
622
+ def update_icon_file(self):
623
+ for file in self.file_paths:
624
+ image_item = self.image_items[file]
625
+ if image_item is not None:
626
+ image_item.update_icon_file()
627
+
628
+ def update_color(self, label_id):
629
+ for file in self.file_paths:
630
+ image_item = self.image_items[file]
631
+ if image_item is not None:
632
+ image_item.update_color(label_id)
633
+ # We save the image in this case
634
+ image_item.save()
635
+ image_item.update_icon_file()
636
+ else:
637
+ #TODO: if a file (ImageItem) is not open, the color is not update.
638
+ # We have to open the good file and change the color of this file (only the file of the good label_id)
639
+ pass
640
+
641
+ def name_already_exists(self, name):
642
+ for label_id, label_item in self.label_items.items():
643
+ if label_item.get_name() == name:
644
+ return True
645
+ return False
646
+
647
+ def save_labels(self, current_file_path):
648
+ # Get the dictionnary of labels
649
+ labels_dict = {}
650
+ for label_id, label_item in self.label_items.items():
651
+ labels_dict[label_id] = label_item.to_dict()
652
+
653
+ # Save it
654
+ with open(current_file_path+os.sep+"labels.json", 'w') as fp:
655
+ json.dump(labels_dict, fp)
656
+
657
+ def save_overlays(self, current_file_path):
658
+ for file in self.file_paths:
659
+ image_item = self.image_items[file]
660
+ if image_item is not None:
661
+ if image_item.get_edited() is True:
662
+ image_item.save_overlays(current_file_path)
663
+ image_item.update_icon_file()
664
+
665
+ def save(self):
666
+ self.save_labels(self.save_directory)
667
+ self.save_overlays(self.save_directory)
668
+
669
+ def load_labels_json(self, file):
670
+ with open(file, "r") as fp:
671
+ labels_dict = json.load(fp)
672
+
673
+ for lid_str, info in labels_dict.items():
674
+ label_id = int(lid_str)
675
+ name = info.get("name")
676
+ labeling_mode = info.get("labeling_mode")
677
+ color_list = info.get("color")
678
+ color = QColor(*color_list[:3]) if isinstance(color_list, list) else QColor(255, 0, 0)
679
+
680
+ if self.name_already_exists(name) or label_id in self.label_items:
681
+ continue
682
+
683
+ self.label_items[label_id] = LabelItem(name, labeling_mode, color, id = label_id)
684
+ self.view.builder.build_new_layer_label_bar(
685
+ label_id, name, labeling_mode, color
686
+ )
687
+
688
+ def load_labels_images(self, label_file_path):
689
+ # filename = os.path.basename(label_file_path)
690
+
691
+ # # Split to get the parts
692
+ # parts = filename.split(KEYWORD_SAVE_LABEL)
693
+ # if len(parts) != 2:
694
+ # return
695
+
696
+ # original_name = parts[0]
697
+ # label_part = parts[1]
698
+ # label_id = label_part.split('.')[0]
699
+
700
+ # # Find the corresponding image file path
701
+ # image_file_path = None
702
+ # for file_path in self.file_paths:
703
+ # base_name = os.path.basename(file_path).split('.')[0]
704
+ # if base_name == original_name:
705
+ # image_file_path = file_path
706
+ # break
707
+
708
+ # Initialize labeling_overview_loaded if needed
709
+
710
+ basename = os.path.basename(label_file_path)
711
+ print("basename:", basename)
712
+ if label_file_path not in self.labeling_overview_was_loaded:
713
+ self.labeling_overview_was_loaded[basename] = False
714
+ self.labeling_overview_file_paths[basename] = label_file_path
715
+ # # Check if we have an ImageItem for this image file
716
+ # if self.image_items[image_file_path] is not None and label_id in self.label_items:
717
+ # image_item = self.image_items[image_file_path]
718
+
719
+ # if os.path.exists(label_file_path):
720
+ # # Remove the existing overlay if it exists
721
+ # if label_id in image_item.labeling_overlays:
722
+ # old_overlay = image_item.labeling_overlays[label_id]
723
+ # old_overlay.remove()
724
+
725
+ # # Create new overlay with the saved file
726
+ # image_item.labeling_overlays[label_id] = LabelingOverlay(
727
+ # self.label_items[label_id],
728
+ # self.view.zoomable_graphics_view.scene,
729
+ # image_item.get_width(),
730
+ # image_item.get_height(),
731
+ # from_file=label_file_path
732
+ # )
733
+
734
+ # # Update the scene to reflect the loaded overlays
735
+ # image_item.update_scene()
736
+
737
+ # # Mark as loaded
738
+ # self.labeling_overview_loaded[image_file_path] = True
739
+
740
+
741
+ def new_label(self, name, labeling_mode, color):
742
+ label = LabelItem(name, labeling_mode, color)
743
+ self.label_items[label.get_label_id()] = label
744
+ return label
745
+
746
+ def update_labeling_overlays(self, selected_label_id):
747
+ print("update_labeling_overlays")
748
+ for file in self.file_paths:
749
+ if self.image_items[file] is not None:
750
+ print("for file:", file)
751
+ self.image_items[file].update_labeling_overlays(self.label_items, selected_label_id)
752
+
753
+ self.current_label_item = self.label_items[selected_label_id]
754
+ if self.current_image_item is not None:
755
+ self.current_image_item.update_scene()
756
+ self.current_image_item.foreground_current_labeling_overlay()
757
+
758
+ def select_image(self, path_image):
759
+ print("select_image")
760
+ if self.checked_button == "contour_filling":
761
+ self.remove_contour()
762
+
763
+ self.zoomable_graphics_view.scene.clear()
764
+ self.zoomable_graphics_view.resetTransform()
765
+ for file in self.file_paths:
766
+ if self.image_items[file] is not None:
767
+ self.image_items[file].clear_scene()
768
+
769
+ if self.image_items[path_image] is None:
770
+ print("select_image new")
771
+ # We have to load image and theses labels
772
+ self.image_items[path_image] = ImageItem(self.view,
773
+ self.controller,
774
+ path_image,
775
+ self.icon_button_files[path_image],
776
+ self.labeling_overview_was_loaded,
777
+ self.labeling_overview_file_paths)
778
+ self.current_image_item = self.image_items[path_image]
779
+ self.image_items[path_image].update_scene()
780
+ if len(self.label_items) != 0:
781
+ self.image_items[path_image].update_labeling_overlays(
782
+ self.label_items,
783
+ self.current_label_item.get_label_id())
784
+ else:
785
+ print("select_image already exists")
786
+ # Image and these labels are already loaded, display it
787
+ self.image_items[path_image].update_scene()
788
+ self.current_image_item = self.image_items[path_image]
789
+
790
+ if self.checked_button == "contour_filling":
791
+ self.apply_contour()
792
+
793
+
794
+
795
+