Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
iperov
GitHub Repository: iperov/deepfacelab
Path: blob/master/XSegEditor/XSegEditor.py
628 views
1
import json
2
import multiprocessing
3
import os
4
import pickle
5
import sys
6
import tempfile
7
import time
8
import traceback
9
from enum import IntEnum
10
from types import SimpleNamespace as sn
11
12
import cv2
13
import numpy as np
14
import numpy.linalg as npla
15
from PyQt5.QtCore import *
16
from PyQt5.QtGui import *
17
from PyQt5.QtWidgets import *
18
19
from core import imagelib, pathex
20
from core.cv2ex import *
21
from core.imagelib import SegIEPoly, SegIEPolys, SegIEPolyType, sd
22
from core.qtex import *
23
from DFLIMG import *
24
from localization import StringsDB, system_language
25
from samplelib import PackedFaceset
26
27
from .QCursorDB import QCursorDB
28
from .QIconDB import QIconDB
29
from .QStringDB import QStringDB
30
from .QImageDB import QImageDB
31
32
class OpMode(IntEnum):
33
NONE = 0
34
DRAW_PTS = 1
35
EDIT_PTS = 2
36
VIEW_BAKED = 3
37
VIEW_XSEG_MASK = 4
38
39
class PTEditMode(IntEnum):
40
MOVE = 0
41
ADD_DEL = 1
42
43
class DragType(IntEnum):
44
NONE = 0
45
IMAGE_LOOK = 1
46
POLY_PT = 2
47
48
class ViewLock(IntEnum):
49
NONE = 0
50
CENTER = 1
51
52
class QUIConfig():
53
@staticmethod
54
def initialize(icon_size = 48, icon_spacer_size=16, preview_bar_icon_size=64):
55
QUIConfig.icon_q_size = QSize(icon_size, icon_size)
56
QUIConfig.icon_spacer_q_size = QSize(icon_spacer_size, icon_spacer_size)
57
QUIConfig.preview_bar_icon_q_size = QSize(preview_bar_icon_size, preview_bar_icon_size)
58
59
class ImagePreviewSequenceBar(QFrame):
60
def __init__(self, preview_images_count, icon_size):
61
super().__init__()
62
self.preview_images_count = preview_images_count = max(1, preview_images_count + (preview_images_count % 2 -1) )
63
64
self.icon_size = icon_size
65
66
black_q_img = QImage(np.zeros( (icon_size,icon_size,3) ).data, icon_size, icon_size, 3*icon_size, QImage.Format_RGB888)
67
self.black_q_pixmap = QPixmap.fromImage(black_q_img)
68
69
self.image_containers = [ QLabel() for i in range(preview_images_count)]
70
71
main_frame_l_cont_hl = QGridLayout()
72
main_frame_l_cont_hl.setContentsMargins(0,0,0,0)
73
#main_frame_l_cont_hl.setSpacing(0)
74
75
76
77
for i in range(len(self.image_containers)):
78
q_label = self.image_containers[i]
79
q_label.setScaledContents(True)
80
if i == preview_images_count//2:
81
q_label.setMinimumSize(icon_size+16, icon_size+16 )
82
q_label.setMaximumSize(icon_size+16, icon_size+16 )
83
else:
84
q_label.setMinimumSize(icon_size, icon_size )
85
q_label.setMaximumSize(icon_size, icon_size )
86
opacity_effect = QGraphicsOpacityEffect()
87
opacity_effect.setOpacity(0.5)
88
q_label.setGraphicsEffect(opacity_effect)
89
90
q_label.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
91
92
main_frame_l_cont_hl.addWidget (q_label, 0, i)
93
94
self.setLayout(main_frame_l_cont_hl)
95
96
self.prev_img_conts = self.image_containers[(preview_images_count//2) -1::-1]
97
self.next_img_conts = self.image_containers[preview_images_count//2:]
98
99
self.update_images()
100
101
def get_preview_images_count(self):
102
return self.preview_images_count
103
104
def update_images(self, prev_imgs=None, next_imgs=None):
105
# Fix arrays
106
if prev_imgs is None:
107
prev_imgs = []
108
prev_img_conts_len = len(self.prev_img_conts)
109
prev_q_imgs_len = len(prev_imgs)
110
if prev_q_imgs_len < prev_img_conts_len:
111
for i in range ( prev_img_conts_len - prev_q_imgs_len ):
112
prev_imgs.append(None)
113
elif prev_q_imgs_len > prev_img_conts_len:
114
prev_imgs = prev_imgs[:prev_img_conts_len]
115
116
if next_imgs is None:
117
next_imgs = []
118
next_img_conts_len = len(self.next_img_conts)
119
next_q_imgs_len = len(next_imgs)
120
if next_q_imgs_len < next_img_conts_len:
121
for i in range ( next_img_conts_len - next_q_imgs_len ):
122
next_imgs.append(None)
123
elif next_q_imgs_len > next_img_conts_len:
124
next_imgs = next_imgs[:next_img_conts_len]
125
126
for i,img in enumerate(prev_imgs):
127
self.prev_img_conts[i].setPixmap( QPixmap.fromImage( QImage_from_np(img) ) if img is not None else self.black_q_pixmap )
128
129
for i,img in enumerate(next_imgs):
130
self.next_img_conts[i].setPixmap( QPixmap.fromImage( QImage_from_np(img) ) if img is not None else self.black_q_pixmap )
131
132
class ColorScheme():
133
def __init__(self, unselected_color, selected_color, outline_color, outline_width, pt_outline_color, cross_cursor):
134
self.poly_unselected_brush = QBrush(unselected_color)
135
self.poly_selected_brush = QBrush(selected_color)
136
137
self.poly_outline_solid_pen = QPen(outline_color, outline_width, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
138
self.poly_outline_dot_pen = QPen(outline_color, outline_width, Qt.DotLine, Qt.RoundCap, Qt.RoundJoin)
139
140
self.pt_outline_pen = QPen(pt_outline_color)
141
self.cross_cursor = cross_cursor
142
143
class CanvasConfig():
144
145
def __init__(self,
146
pt_radius=4,
147
pt_select_radius=8,
148
color_schemes=None,
149
**kwargs):
150
self.pt_radius = pt_radius
151
self.pt_select_radius = pt_select_radius
152
153
if color_schemes is None:
154
color_schemes = [
155
ColorScheme( QColor(192,0,0,alpha=0), QColor(192,0,0,alpha=72), QColor(192,0,0), 2, QColor(255,255,255), QCursorDB.cross_red ),
156
ColorScheme( QColor(0,192,0,alpha=0), QColor(0,192,0,alpha=72), QColor(0,192,0), 2, QColor(255,255,255), QCursorDB.cross_green ),
157
ColorScheme( QColor(0,0,192,alpha=0), QColor(0,0,192,alpha=72), QColor(0,0,192), 2, QColor(255,255,255), QCursorDB.cross_blue ),
158
]
159
self.color_schemes = color_schemes
160
161
class QCanvasControlsLeftBar(QFrame):
162
163
def __init__(self):
164
super().__init__()
165
#==============================================
166
btn_poly_type_include = QToolButton()
167
self.btn_poly_type_include_act = QActionEx( QIconDB.poly_type_include, QStringDB.btn_poly_type_include_tip, shortcut='Q', shortcut_in_tooltip=True, is_checkable=True)
168
btn_poly_type_include.setDefaultAction(self.btn_poly_type_include_act)
169
btn_poly_type_include.setIconSize(QUIConfig.icon_q_size)
170
171
btn_poly_type_exclude = QToolButton()
172
self.btn_poly_type_exclude_act = QActionEx( QIconDB.poly_type_exclude, QStringDB.btn_poly_type_exclude_tip, shortcut='W', shortcut_in_tooltip=True, is_checkable=True)
173
btn_poly_type_exclude.setDefaultAction(self.btn_poly_type_exclude_act)
174
btn_poly_type_exclude.setIconSize(QUIConfig.icon_q_size)
175
176
self.btn_poly_type_act_grp = QActionGroup (self)
177
self.btn_poly_type_act_grp.addAction(self.btn_poly_type_include_act)
178
self.btn_poly_type_act_grp.addAction(self.btn_poly_type_exclude_act)
179
self.btn_poly_type_act_grp.setExclusive(True)
180
#==============================================
181
btn_undo_pt = QToolButton()
182
self.btn_undo_pt_act = QActionEx( QIconDB.undo_pt, QStringDB.btn_undo_pt_tip, shortcut='Ctrl+Z', shortcut_in_tooltip=True, is_auto_repeat=True)
183
btn_undo_pt.setDefaultAction(self.btn_undo_pt_act)
184
btn_undo_pt.setIconSize(QUIConfig.icon_q_size)
185
186
btn_redo_pt = QToolButton()
187
self.btn_redo_pt_act = QActionEx( QIconDB.redo_pt, QStringDB.btn_redo_pt_tip, shortcut='Ctrl+Shift+Z', shortcut_in_tooltip=True, is_auto_repeat=True)
188
btn_redo_pt.setDefaultAction(self.btn_redo_pt_act)
189
btn_redo_pt.setIconSize(QUIConfig.icon_q_size)
190
191
btn_delete_poly = QToolButton()
192
self.btn_delete_poly_act = QActionEx( QIconDB.delete_poly, QStringDB.btn_delete_poly_tip, shortcut='Delete', shortcut_in_tooltip=True)
193
btn_delete_poly.setDefaultAction(self.btn_delete_poly_act)
194
btn_delete_poly.setIconSize(QUIConfig.icon_q_size)
195
#==============================================
196
btn_pt_edit_mode = QToolButton()
197
self.btn_pt_edit_mode_act = QActionEx( QIconDB.pt_edit_mode, QStringDB.btn_pt_edit_mode_tip, shortcut_in_tooltip=True, is_checkable=True)
198
btn_pt_edit_mode.setDefaultAction(self.btn_pt_edit_mode_act)
199
btn_pt_edit_mode.setIconSize(QUIConfig.icon_q_size)
200
#==============================================
201
202
controls_bar_frame2_l = QVBoxLayout()
203
controls_bar_frame2_l.addWidget ( btn_poly_type_include )
204
controls_bar_frame2_l.addWidget ( btn_poly_type_exclude )
205
controls_bar_frame2 = QFrame()
206
controls_bar_frame2.setFrameShape(QFrame.StyledPanel)
207
controls_bar_frame2.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
208
controls_bar_frame2.setLayout(controls_bar_frame2_l)
209
210
controls_bar_frame3_l = QVBoxLayout()
211
controls_bar_frame3_l.addWidget ( btn_undo_pt )
212
controls_bar_frame3_l.addWidget ( btn_redo_pt )
213
controls_bar_frame3_l.addWidget ( btn_delete_poly )
214
controls_bar_frame3 = QFrame()
215
controls_bar_frame3.setFrameShape(QFrame.StyledPanel)
216
controls_bar_frame3.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
217
controls_bar_frame3.setLayout(controls_bar_frame3_l)
218
219
controls_bar_frame4_l = QVBoxLayout()
220
controls_bar_frame4_l.addWidget ( btn_pt_edit_mode )
221
controls_bar_frame4 = QFrame()
222
controls_bar_frame4.setFrameShape(QFrame.StyledPanel)
223
controls_bar_frame4.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
224
controls_bar_frame4.setLayout(controls_bar_frame4_l)
225
226
controls_bar_l = QVBoxLayout()
227
controls_bar_l.setContentsMargins(0,0,0,0)
228
controls_bar_l.addWidget(controls_bar_frame2)
229
controls_bar_l.addWidget(controls_bar_frame3)
230
controls_bar_l.addWidget(controls_bar_frame4)
231
232
self.setSizePolicy ( QSizePolicy.Fixed, QSizePolicy.Expanding )
233
self.setLayout(controls_bar_l)
234
235
class QCanvasControlsRightBar(QFrame):
236
237
def __init__(self):
238
super().__init__()
239
#==============================================
240
btn_poly_color_red = QToolButton()
241
self.btn_poly_color_red_act = QActionEx( QIconDB.poly_color_red, QStringDB.btn_poly_color_red_tip, shortcut='1', shortcut_in_tooltip=True, is_checkable=True)
242
btn_poly_color_red.setDefaultAction(self.btn_poly_color_red_act)
243
btn_poly_color_red.setIconSize(QUIConfig.icon_q_size)
244
245
btn_poly_color_green = QToolButton()
246
self.btn_poly_color_green_act = QActionEx( QIconDB.poly_color_green, QStringDB.btn_poly_color_green_tip, shortcut='2', shortcut_in_tooltip=True, is_checkable=True)
247
btn_poly_color_green.setDefaultAction(self.btn_poly_color_green_act)
248
btn_poly_color_green.setIconSize(QUIConfig.icon_q_size)
249
250
btn_poly_color_blue = QToolButton()
251
self.btn_poly_color_blue_act = QActionEx( QIconDB.poly_color_blue, QStringDB.btn_poly_color_blue_tip, shortcut='3', shortcut_in_tooltip=True, is_checkable=True)
252
btn_poly_color_blue.setDefaultAction(self.btn_poly_color_blue_act)
253
btn_poly_color_blue.setIconSize(QUIConfig.icon_q_size)
254
255
btn_view_baked_mask = QToolButton()
256
self.btn_view_baked_mask_act = QActionEx( QIconDB.view_baked, QStringDB.btn_view_baked_mask_tip, shortcut='4', shortcut_in_tooltip=True, is_checkable=True)
257
btn_view_baked_mask.setDefaultAction(self.btn_view_baked_mask_act)
258
btn_view_baked_mask.setIconSize(QUIConfig.icon_q_size)
259
260
btn_view_xseg_mask = QToolButton()
261
self.btn_view_xseg_mask_act = QActionEx( QIconDB.view_xseg, QStringDB.btn_view_xseg_mask_tip, shortcut='5', shortcut_in_tooltip=True, is_checkable=True)
262
btn_view_xseg_mask.setDefaultAction(self.btn_view_xseg_mask_act)
263
btn_view_xseg_mask.setIconSize(QUIConfig.icon_q_size)
264
265
btn_view_xseg_overlay_mask = QToolButton()
266
self.btn_view_xseg_overlay_mask_act = QActionEx( QIconDB.view_xseg_overlay, QStringDB.btn_view_xseg_overlay_mask_tip, shortcut='`', shortcut_in_tooltip=True, is_checkable=True)
267
btn_view_xseg_overlay_mask.setDefaultAction(self.btn_view_xseg_overlay_mask_act)
268
btn_view_xseg_overlay_mask.setIconSize(QUIConfig.icon_q_size)
269
270
self.btn_poly_color_act_grp = QActionGroup (self)
271
self.btn_poly_color_act_grp.addAction(self.btn_poly_color_red_act)
272
self.btn_poly_color_act_grp.addAction(self.btn_poly_color_green_act)
273
self.btn_poly_color_act_grp.addAction(self.btn_poly_color_blue_act)
274
self.btn_poly_color_act_grp.addAction(self.btn_view_baked_mask_act)
275
self.btn_poly_color_act_grp.addAction(self.btn_view_xseg_mask_act)
276
self.btn_poly_color_act_grp.setExclusive(True)
277
#==============================================
278
btn_view_lock_center = QToolButton()
279
self.btn_view_lock_center_act = QActionEx( QIconDB.view_lock_center, QStringDB.btn_view_lock_center_tip, shortcut_in_tooltip=True, is_checkable=True)
280
btn_view_lock_center.setDefaultAction(self.btn_view_lock_center_act)
281
btn_view_lock_center.setIconSize(QUIConfig.icon_q_size)
282
283
controls_bar_frame2_l = QVBoxLayout()
284
controls_bar_frame2_l.addWidget ( btn_view_xseg_overlay_mask )
285
controls_bar_frame2 = QFrame()
286
controls_bar_frame2.setFrameShape(QFrame.StyledPanel)
287
controls_bar_frame2.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
288
controls_bar_frame2.setLayout(controls_bar_frame2_l)
289
290
controls_bar_frame1_l = QVBoxLayout()
291
controls_bar_frame1_l.addWidget ( btn_poly_color_red )
292
controls_bar_frame1_l.addWidget ( btn_poly_color_green )
293
controls_bar_frame1_l.addWidget ( btn_poly_color_blue )
294
controls_bar_frame1_l.addWidget ( btn_view_baked_mask )
295
controls_bar_frame1_l.addWidget ( btn_view_xseg_mask )
296
controls_bar_frame1 = QFrame()
297
controls_bar_frame1.setFrameShape(QFrame.StyledPanel)
298
controls_bar_frame1.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
299
controls_bar_frame1.setLayout(controls_bar_frame1_l)
300
301
controls_bar_frame3_l = QVBoxLayout()
302
controls_bar_frame3_l.addWidget ( btn_view_lock_center )
303
controls_bar_frame3 = QFrame()
304
controls_bar_frame3.setFrameShape(QFrame.StyledPanel)
305
controls_bar_frame3.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
306
controls_bar_frame3.setLayout(controls_bar_frame3_l)
307
308
controls_bar_l = QVBoxLayout()
309
controls_bar_l.setContentsMargins(0,0,0,0)
310
controls_bar_l.addWidget(controls_bar_frame2)
311
controls_bar_l.addWidget(controls_bar_frame1)
312
controls_bar_l.addWidget(controls_bar_frame3)
313
314
self.setSizePolicy ( QSizePolicy.Fixed, QSizePolicy.Expanding )
315
self.setLayout(controls_bar_l)
316
317
class QCanvasOperator(QWidget):
318
def __init__(self, cbar):
319
super().__init__()
320
self.cbar = cbar
321
322
self.set_cbar_disabled()
323
324
self.cbar.btn_poly_color_red_act.triggered.connect ( lambda : self.set_color_scheme_id(0) )
325
self.cbar.btn_poly_color_green_act.triggered.connect ( lambda : self.set_color_scheme_id(1) )
326
self.cbar.btn_poly_color_blue_act.triggered.connect ( lambda : self.set_color_scheme_id(2) )
327
self.cbar.btn_view_baked_mask_act.triggered.connect ( lambda : self.set_op_mode(OpMode.VIEW_BAKED) )
328
self.cbar.btn_view_xseg_mask_act.triggered.connect ( lambda : self.set_op_mode(OpMode.VIEW_XSEG_MASK) )
329
330
self.cbar.btn_view_xseg_overlay_mask_act.toggled.connect ( lambda is_checked: self.update() )
331
332
self.cbar.btn_poly_type_include_act.triggered.connect ( lambda : self.set_poly_include_type(SegIEPolyType.INCLUDE) )
333
self.cbar.btn_poly_type_exclude_act.triggered.connect ( lambda : self.set_poly_include_type(SegIEPolyType.EXCLUDE) )
334
335
self.cbar.btn_undo_pt_act.triggered.connect ( lambda : self.action_undo_pt() )
336
self.cbar.btn_redo_pt_act.triggered.connect ( lambda : self.action_redo_pt() )
337
338
self.cbar.btn_delete_poly_act.triggered.connect ( lambda : self.action_delete_poly() )
339
340
self.cbar.btn_pt_edit_mode_act.toggled.connect ( lambda is_checked: self.set_pt_edit_mode( PTEditMode.ADD_DEL if is_checked else PTEditMode.MOVE ) )
341
self.cbar.btn_view_lock_center_act.toggled.connect ( lambda is_checked: self.set_view_lock( ViewLock.CENTER if is_checked else ViewLock.NONE ) )
342
343
self.mouse_in_widget = False
344
345
QXMainWindow.inst.add_keyPressEvent_listener ( self.on_keyPressEvent )
346
QXMainWindow.inst.add_keyReleaseEvent_listener ( self.on_keyReleaseEvent )
347
348
self.qp = QPainter()
349
self.initialized = False
350
self.last_state = None
351
352
def initialize(self, img, img_look_pt=None, view_scale=None, ie_polys=None, xseg_mask=None, canvas_config=None ):
353
q_img = self.q_img = QImage_from_np(img)
354
self.img_pixmap = QPixmap.fromImage(q_img)
355
356
self.xseg_mask_pixmap = None
357
self.xseg_overlay_mask_pixmap = None
358
if xseg_mask is not None:
359
h,w,c = img.shape
360
xseg_mask = cv2.resize(xseg_mask, (w,h), interpolation=cv2.INTER_CUBIC)
361
xseg_mask = imagelib.normalize_channels(xseg_mask, 1)
362
xseg_img = img.astype(np.float32)/255.0
363
xseg_overlay_mask = xseg_img*(1-xseg_mask)*0.5 + xseg_img*xseg_mask
364
xseg_overlay_mask = np.clip(xseg_overlay_mask*255, 0, 255).astype(np.uint8)
365
xseg_mask = np.clip(xseg_mask*255, 0, 255).astype(np.uint8)
366
self.xseg_mask_pixmap = QPixmap.fromImage(QImage_from_np(xseg_mask))
367
self.xseg_overlay_mask_pixmap = QPixmap.fromImage(QImage_from_np(xseg_overlay_mask))
368
369
self.img_size = QSize_to_np (self.img_pixmap.size())
370
371
self.img_look_pt = img_look_pt
372
self.view_scale = view_scale
373
374
if ie_polys is None:
375
ie_polys = SegIEPolys()
376
self.ie_polys = ie_polys
377
378
if canvas_config is None:
379
canvas_config = CanvasConfig()
380
self.canvas_config = canvas_config
381
382
# UI init
383
self.set_cbar_disabled()
384
self.cbar.btn_poly_color_act_grp.setDisabled(False)
385
self.cbar.btn_view_xseg_overlay_mask_act.setDisabled(False)
386
self.cbar.btn_poly_type_act_grp.setDisabled(False)
387
388
# Initial vars
389
self.current_cursor = None
390
self.mouse_hull_poly = None
391
self.mouse_wire_poly = None
392
self.drag_type = DragType.NONE
393
self.mouse_cli_pt = np.zeros((2,), np.float32 )
394
395
# Initial state
396
self.set_op_mode(OpMode.NONE)
397
self.set_color_scheme_id(1)
398
self.set_poly_include_type(SegIEPolyType.INCLUDE)
399
self.set_pt_edit_mode(PTEditMode.MOVE)
400
self.set_view_lock(ViewLock.NONE)
401
402
# Apply last state
403
if self.last_state is not None:
404
self.set_color_scheme_id(self.last_state.color_scheme_id)
405
if self.last_state.op_mode is not None:
406
self.set_op_mode(self.last_state.op_mode)
407
408
self.initialized = True
409
410
self.setMouseTracking(True)
411
self.update_cursor()
412
self.update()
413
414
415
def finalize(self):
416
if self.initialized:
417
if self.op_mode == OpMode.DRAW_PTS:
418
self.set_op_mode(OpMode.EDIT_PTS)
419
420
self.last_state = sn(op_mode = self.op_mode if self.op_mode in [OpMode.VIEW_BAKED, OpMode.VIEW_XSEG_MASK] else None,
421
color_scheme_id = self.color_scheme_id)
422
423
self.img_pixmap = None
424
self.update_cursor(is_finalize=True)
425
self.setMouseTracking(False)
426
self.setFocusPolicy(Qt.NoFocus)
427
self.set_cbar_disabled()
428
self.initialized = False
429
self.update()
430
431
# ====================================================================================
432
# ====================================================================================
433
# ====================================== GETTERS =====================================
434
# ====================================================================================
435
# ====================================================================================
436
def is_initialized(self):
437
return self.initialized
438
439
def get_ie_polys(self):
440
return self.ie_polys
441
442
def get_cli_center_pt(self):
443
return np.round(QSize_to_np(self.size())/2.0)
444
445
def get_img_look_pt(self):
446
img_look_pt = self.img_look_pt
447
if img_look_pt is None:
448
img_look_pt = self.img_size / 2
449
return img_look_pt
450
451
def get_view_scale(self):
452
view_scale = self.view_scale
453
if view_scale is None:
454
# Calc as scale to fit
455
min_cli_size = np.min(QSize_to_np(self.size()))
456
max_img_size = np.max(self.img_size)
457
view_scale = min_cli_size / max_img_size
458
459
return view_scale
460
461
def get_current_color_scheme(self):
462
return self.canvas_config.color_schemes[self.color_scheme_id]
463
464
def get_poly_pt_id_under_pt(self, poly, cli_pt):
465
w = np.argwhere ( npla.norm ( cli_pt - self.img_to_cli_pt( poly.get_pts() ), axis=1 ) <= self.canvas_config.pt_select_radius )
466
return None if len(w) == 0 else w[-1][0]
467
468
def get_poly_edge_id_pt_under_pt(self, poly, cli_pt):
469
cli_pts = self.img_to_cli_pt(poly.get_pts())
470
if len(cli_pts) >= 3:
471
edge_dists, projs = sd.dist_to_edges(cli_pts, cli_pt, is_closed=True)
472
edge_id = np.argmin(edge_dists)
473
dist = edge_dists[edge_id]
474
pt = projs[edge_id]
475
if dist <= self.canvas_config.pt_select_radius:
476
return edge_id, pt
477
return None, None
478
479
def get_poly_by_pt_near_wire(self, cli_pt):
480
pt_select_radius = self.canvas_config.pt_select_radius
481
482
for poly in reversed(self.ie_polys.get_polys()):
483
pts = poly.get_pts()
484
if len(pts) >= 3:
485
cli_pts = self.img_to_cli_pt(pts)
486
487
edge_dists, _ = sd.dist_to_edges(cli_pts, cli_pt, is_closed=True)
488
489
if np.min(edge_dists) <= pt_select_radius or \
490
any( npla.norm ( cli_pt - cli_pts, axis=1 ) <= pt_select_radius ):
491
return poly
492
return None
493
494
def get_poly_by_pt_in_hull(self, cli_pos):
495
img_pos = self.cli_to_img_pt(cli_pos)
496
497
for poly in reversed(self.ie_polys.get_polys()):
498
pts = poly.get_pts()
499
if len(pts) >= 3:
500
if cv2.pointPolygonTest( pts, tuple(img_pos), False) >= 0:
501
return poly
502
503
return None
504
505
def img_to_cli_pt(self, p):
506
return (p - self.get_img_look_pt()) * self.get_view_scale() + self.get_cli_center_pt()# QSize_to_np(self.size())/2.0
507
508
def cli_to_img_pt(self, p):
509
return (p - self.get_cli_center_pt() ) / self.get_view_scale() + self.get_img_look_pt()
510
511
def img_to_cli_rect(self, rect):
512
tl = QPoint_to_np(rect.topLeft())
513
xy = self.img_to_cli_pt(tl)
514
xy2 = self.img_to_cli_pt(tl + QSize_to_np(rect.size()) ) - xy
515
return QRect ( *xy.astype(np.int), *xy2.astype(np.int) )
516
517
# ====================================================================================
518
# ====================================================================================
519
# ====================================== SETTERS =====================================
520
# ====================================================================================
521
# ====================================================================================
522
def set_op_mode(self, op_mode, op_poly=None):
523
if not hasattr(self,'op_mode'):
524
self.op_mode = None
525
self.op_poly = None
526
527
if self.op_mode != op_mode:
528
# Finalize prev mode
529
if self.op_mode == OpMode.NONE:
530
self.cbar.btn_poly_type_act_grp.setDisabled(True)
531
elif self.op_mode == OpMode.DRAW_PTS:
532
self.cbar.btn_undo_pt_act.setDisabled(True)
533
self.cbar.btn_redo_pt_act.setDisabled(True)
534
self.cbar.btn_view_lock_center_act.setDisabled(True)
535
# Reset view_lock when exit from DRAW_PTS
536
self.set_view_lock(ViewLock.NONE)
537
# Remove unfinished poly
538
if self.op_poly.get_pts_count() < 3:
539
self.ie_polys.remove_poly(self.op_poly)
540
541
elif self.op_mode == OpMode.EDIT_PTS:
542
self.cbar.btn_pt_edit_mode_act.setDisabled(True)
543
self.cbar.btn_delete_poly_act.setDisabled(True)
544
# Reset pt_edit_move when exit from EDIT_PTS
545
self.set_pt_edit_mode(PTEditMode.MOVE)
546
elif self.op_mode == OpMode.VIEW_BAKED:
547
self.cbar.btn_view_baked_mask_act.setChecked(False)
548
elif self.op_mode == OpMode.VIEW_XSEG_MASK:
549
self.cbar.btn_view_xseg_mask_act.setChecked(False)
550
551
self.op_mode = op_mode
552
553
# Initialize new mode
554
if op_mode == OpMode.NONE:
555
self.cbar.btn_poly_type_act_grp.setDisabled(False)
556
elif op_mode == OpMode.DRAW_PTS:
557
self.cbar.btn_undo_pt_act.setDisabled(False)
558
self.cbar.btn_redo_pt_act.setDisabled(False)
559
self.cbar.btn_view_lock_center_act.setDisabled(False)
560
elif op_mode == OpMode.EDIT_PTS:
561
self.cbar.btn_pt_edit_mode_act.setDisabled(False)
562
self.cbar.btn_delete_poly_act.setDisabled(False)
563
elif op_mode == OpMode.VIEW_BAKED:
564
self.cbar.btn_view_baked_mask_act.setChecked(True )
565
n = QImage_to_np ( self.q_img ).astype(np.float32) / 255.0
566
h,w,c = n.shape
567
mask = np.zeros( (h,w,1), dtype=np.float32 )
568
self.ie_polys.overlay_mask(mask)
569
n = (mask*255).astype(np.uint8)
570
self.img_baked_pixmap = QPixmap.fromImage(QImage_from_np(n))
571
elif op_mode == OpMode.VIEW_XSEG_MASK:
572
self.cbar.btn_view_xseg_mask_act.setChecked(True)
573
574
if op_mode in [OpMode.DRAW_PTS, OpMode.EDIT_PTS]:
575
self.mouse_op_poly_pt_id = None
576
self.mouse_op_poly_edge_id = None
577
self.mouse_op_poly_edge_id_pt = None
578
579
self.op_poly = op_poly
580
if op_poly is not None:
581
self.update_mouse_info()
582
583
self.update_cursor()
584
self.update()
585
586
def set_pt_edit_mode(self, pt_edit_mode):
587
if not hasattr(self, 'pt_edit_mode') or self.pt_edit_mode != pt_edit_mode:
588
self.pt_edit_mode = pt_edit_mode
589
self.update_cursor()
590
self.update()
591
self.cbar.btn_pt_edit_mode_act.setChecked( self.pt_edit_mode == PTEditMode.ADD_DEL )
592
593
def set_view_lock(self, view_lock):
594
if not hasattr(self, 'view_lock') or self.view_lock != view_lock:
595
if hasattr(self, 'view_lock') and self.view_lock != view_lock:
596
if view_lock == ViewLock.CENTER:
597
self.img_look_pt = self.mouse_img_pt
598
QCursor.setPos ( self.mapToGlobal( QPoint_from_np(self.img_to_cli_pt(self.img_look_pt)) ))
599
600
self.view_lock = view_lock
601
self.update()
602
self.cbar.btn_view_lock_center_act.setChecked( self.view_lock == ViewLock.CENTER )
603
604
def set_cbar_disabled(self):
605
self.cbar.btn_delete_poly_act.setDisabled(True)
606
self.cbar.btn_undo_pt_act.setDisabled(True)
607
self.cbar.btn_redo_pt_act.setDisabled(True)
608
self.cbar.btn_pt_edit_mode_act.setDisabled(True)
609
self.cbar.btn_view_lock_center_act.setDisabled(True)
610
self.cbar.btn_poly_color_act_grp.setDisabled(True)
611
self.cbar.btn_view_xseg_overlay_mask_act.setDisabled(True)
612
self.cbar.btn_poly_type_act_grp.setDisabled(True)
613
614
615
def set_color_scheme_id(self, id):
616
if self.op_mode == OpMode.VIEW_BAKED or self.op_mode == OpMode.VIEW_XSEG_MASK:
617
self.set_op_mode(OpMode.NONE)
618
619
if not hasattr(self, 'color_scheme_id') or self.color_scheme_id != id:
620
self.color_scheme_id = id
621
self.update_cursor()
622
self.update()
623
624
if self.color_scheme_id == 0:
625
self.cbar.btn_poly_color_red_act.setChecked( True )
626
elif self.color_scheme_id == 1:
627
self.cbar.btn_poly_color_green_act.setChecked( True )
628
elif self.color_scheme_id == 2:
629
self.cbar.btn_poly_color_blue_act.setChecked( True )
630
631
def set_poly_include_type(self, poly_include_type):
632
if not hasattr(self, 'poly_include_type' ) or \
633
( self.poly_include_type != poly_include_type and \
634
self.op_mode in [OpMode.NONE, OpMode.EDIT_PTS] ):
635
self.poly_include_type = poly_include_type
636
self.update()
637
self.cbar.btn_poly_type_include_act.setChecked(self.poly_include_type == SegIEPolyType.INCLUDE)
638
self.cbar.btn_poly_type_exclude_act.setChecked(self.poly_include_type == SegIEPolyType.EXCLUDE)
639
640
# ====================================================================================
641
# ====================================================================================
642
# ====================================== METHODS =====================================
643
# ====================================================================================
644
# ====================================================================================
645
646
def update_cursor(self, is_finalize=False):
647
if not self.initialized:
648
return
649
650
if not self.mouse_in_widget or is_finalize:
651
if self.current_cursor is not None:
652
QApplication.restoreOverrideCursor()
653
self.current_cursor = None
654
else:
655
color_cc = self.get_current_color_scheme().cross_cursor
656
nc = Qt.ArrowCursor
657
658
if self.drag_type == DragType.IMAGE_LOOK:
659
nc = Qt.ClosedHandCursor
660
else:
661
662
if self.op_mode == OpMode.NONE:
663
nc = color_cc
664
if self.mouse_wire_poly is not None:
665
nc = Qt.PointingHandCursor
666
667
elif self.op_mode == OpMode.DRAW_PTS:
668
nc = color_cc
669
elif self.op_mode == OpMode.EDIT_PTS:
670
nc = Qt.ArrowCursor
671
672
if self.mouse_op_poly_pt_id is not None:
673
nc = Qt.PointingHandCursor
674
675
if self.pt_edit_mode == PTEditMode.ADD_DEL:
676
677
if self.mouse_op_poly_edge_id is not None and \
678
self.mouse_op_poly_pt_id is None:
679
nc = color_cc
680
if self.current_cursor != nc:
681
if self.current_cursor is None:
682
QApplication.setOverrideCursor(nc)
683
else:
684
QApplication.changeOverrideCursor(nc)
685
self.current_cursor = nc
686
687
def update_mouse_info(self, mouse_cli_pt=None):
688
"""
689
Update selected polys/edges/points by given mouse position
690
"""
691
if mouse_cli_pt is not None:
692
self.mouse_cli_pt = mouse_cli_pt.astype(np.float32)
693
694
self.mouse_img_pt = self.cli_to_img_pt(self.mouse_cli_pt)
695
696
new_mouse_hull_poly = self.get_poly_by_pt_in_hull(self.mouse_cli_pt)
697
698
if self.mouse_hull_poly != new_mouse_hull_poly:
699
self.mouse_hull_poly = new_mouse_hull_poly
700
self.update_cursor()
701
self.update()
702
703
new_mouse_wire_poly = self.get_poly_by_pt_near_wire(self.mouse_cli_pt)
704
705
if self.mouse_wire_poly != new_mouse_wire_poly:
706
self.mouse_wire_poly = new_mouse_wire_poly
707
self.update_cursor()
708
self.update()
709
710
if self.op_mode in [OpMode.DRAW_PTS, OpMode.EDIT_PTS]:
711
new_mouse_op_poly_pt_id = self.get_poly_pt_id_under_pt (self.op_poly, self.mouse_cli_pt)
712
if self.mouse_op_poly_pt_id != new_mouse_op_poly_pt_id:
713
self.mouse_op_poly_pt_id = new_mouse_op_poly_pt_id
714
self.update_cursor()
715
self.update()
716
717
new_mouse_op_poly_edge_id,\
718
new_mouse_op_poly_edge_id_pt = self.get_poly_edge_id_pt_under_pt (self.op_poly, self.mouse_cli_pt)
719
if self.mouse_op_poly_edge_id != new_mouse_op_poly_edge_id:
720
self.mouse_op_poly_edge_id = new_mouse_op_poly_edge_id
721
self.update_cursor()
722
self.update()
723
724
if (self.mouse_op_poly_edge_id_pt.__class__ != new_mouse_op_poly_edge_id_pt.__class__) or \
725
(isinstance(self.mouse_op_poly_edge_id_pt, np.ndarray) and \
726
all(self.mouse_op_poly_edge_id_pt != new_mouse_op_poly_edge_id_pt)):
727
728
self.mouse_op_poly_edge_id_pt = new_mouse_op_poly_edge_id_pt
729
self.update_cursor()
730
self.update()
731
732
733
def action_undo_pt(self):
734
if self.drag_type == DragType.NONE:
735
if self.op_mode == OpMode.DRAW_PTS:
736
if self.op_poly.undo() == 0:
737
self.ie_polys.remove_poly (self.op_poly)
738
self.set_op_mode(OpMode.NONE)
739
self.update()
740
741
def action_redo_pt(self):
742
if self.drag_type == DragType.NONE:
743
if self.op_mode == OpMode.DRAW_PTS:
744
self.op_poly.redo()
745
self.update()
746
747
def action_delete_poly(self):
748
if self.op_mode == OpMode.EDIT_PTS and \
749
self.drag_type == DragType.NONE and \
750
self.pt_edit_mode == PTEditMode.MOVE:
751
# Delete current poly
752
self.ie_polys.remove_poly (self.op_poly)
753
self.set_op_mode(OpMode.NONE)
754
755
# ====================================================================================
756
# ====================================================================================
757
# ================================== OVERRIDE QT METHODS =============================
758
# ====================================================================================
759
# ====================================================================================
760
def on_keyPressEvent(self, ev):
761
if not self.initialized:
762
return
763
key = ev.key()
764
key_mods = int(ev.modifiers())
765
if self.op_mode == OpMode.DRAW_PTS:
766
self.set_view_lock(ViewLock.CENTER if key_mods == Qt.ShiftModifier else ViewLock.NONE )
767
elif self.op_mode == OpMode.EDIT_PTS:
768
self.set_pt_edit_mode(PTEditMode.ADD_DEL if key_mods == Qt.ControlModifier else PTEditMode.MOVE )
769
770
def on_keyReleaseEvent(self, ev):
771
if not self.initialized:
772
return
773
key = ev.key()
774
key_mods = int(ev.modifiers())
775
if self.op_mode == OpMode.DRAW_PTS:
776
self.set_view_lock(ViewLock.CENTER if key_mods == Qt.ShiftModifier else ViewLock.NONE )
777
elif self.op_mode == OpMode.EDIT_PTS:
778
self.set_pt_edit_mode(PTEditMode.ADD_DEL if key_mods == Qt.ControlModifier else PTEditMode.MOVE )
779
780
def enterEvent(self, ev):
781
super().enterEvent(ev)
782
self.mouse_in_widget = True
783
self.update_cursor()
784
785
def leaveEvent(self, ev):
786
super().leaveEvent(ev)
787
self.mouse_in_widget = False
788
self.update_cursor()
789
790
def mousePressEvent(self, ev):
791
super().mousePressEvent(ev)
792
if not self.initialized:
793
return
794
795
self.update_mouse_info(QPoint_to_np(ev.pos()))
796
797
btn = ev.button()
798
799
if btn == Qt.LeftButton:
800
if self.op_mode == OpMode.NONE:
801
# Clicking in NO OPERATION mode
802
if self.mouse_wire_poly is not None:
803
# Click on wire on any poly -> switch to EDIT_MODE
804
self.set_op_mode(OpMode.EDIT_PTS, op_poly=self.mouse_wire_poly)
805
else:
806
# Click on empty space -> create new poly with one point
807
new_poly = self.ie_polys.add_poly(self.poly_include_type)
808
self.ie_polys.sort()
809
new_poly.add_pt(*self.mouse_img_pt)
810
self.set_op_mode(OpMode.DRAW_PTS, op_poly=new_poly )
811
812
elif self.op_mode == OpMode.DRAW_PTS:
813
# Clicking in DRAW_PTS mode
814
if len(self.op_poly.get_pts()) >= 3 and self.mouse_op_poly_pt_id == 0:
815
# Click on first point -> close poly and switch to edit mode
816
self.set_op_mode(OpMode.EDIT_PTS, op_poly=self.op_poly)
817
else:
818
# Click on empty space -> add point to current poly
819
self.op_poly.add_pt(*self.mouse_img_pt)
820
self.update()
821
822
elif self.op_mode == OpMode.EDIT_PTS:
823
# Clicking in EDIT_PTS mode
824
825
if self.mouse_op_poly_pt_id is not None:
826
# Click on point of op_poly
827
if self.pt_edit_mode == PTEditMode.ADD_DEL:
828
# in mode 'delete point'
829
self.op_poly.remove_pt(self.mouse_op_poly_pt_id)
830
if self.op_poly.get_pts_count() < 3:
831
# not enough points after delete -> remove poly
832
self.ie_polys.remove_poly (self.op_poly)
833
self.set_op_mode(OpMode.NONE)
834
self.update()
835
836
elif self.drag_type == DragType.NONE:
837
# otherwise -> start drag
838
self.drag_type = DragType.POLY_PT
839
self.drag_cli_pt = self.mouse_cli_pt
840
self.drag_poly_pt_id = self.mouse_op_poly_pt_id
841
self.drag_poly_pt = self.op_poly.get_pts()[ self.drag_poly_pt_id ]
842
elif self.mouse_op_poly_edge_id is not None:
843
# Click on edge of op_poly
844
if self.pt_edit_mode == PTEditMode.ADD_DEL:
845
# in mode 'insert new point'
846
edge_img_pt = self.cli_to_img_pt(self.mouse_op_poly_edge_id_pt)
847
self.op_poly.insert_pt (self.mouse_op_poly_edge_id+1, edge_img_pt)
848
self.update()
849
else:
850
# Otherwise do nothing
851
pass
852
else:
853
# other cases -> unselect poly
854
self.set_op_mode(OpMode.NONE)
855
856
elif btn == Qt.MiddleButton:
857
if self.drag_type == DragType.NONE:
858
# Start image drag
859
self.drag_type = DragType.IMAGE_LOOK
860
self.drag_cli_pt = self.mouse_cli_pt
861
self.drag_img_look_pt = self.get_img_look_pt()
862
self.update_cursor()
863
864
865
def mouseReleaseEvent(self, ev):
866
super().mouseReleaseEvent(ev)
867
if not self.initialized:
868
return
869
870
self.update_mouse_info(QPoint_to_np(ev.pos()))
871
872
btn = ev.button()
873
874
if btn == Qt.LeftButton:
875
if self.op_mode == OpMode.EDIT_PTS:
876
if self.drag_type == DragType.POLY_PT:
877
self.drag_type = DragType.NONE
878
self.update()
879
880
elif btn == Qt.MiddleButton:
881
if self.drag_type == DragType.IMAGE_LOOK:
882
self.drag_type = DragType.NONE
883
self.update_cursor()
884
self.update()
885
886
def mouseMoveEvent(self, ev):
887
super().mouseMoveEvent(ev)
888
if not self.initialized:
889
return
890
891
prev_mouse_cli_pt = self.mouse_cli_pt
892
self.update_mouse_info(QPoint_to_np(ev.pos()))
893
894
if self.view_lock == ViewLock.CENTER:
895
if npla.norm(self.mouse_cli_pt - prev_mouse_cli_pt) >= 1:
896
self.img_look_pt = self.mouse_img_pt
897
QCursor.setPos ( self.mapToGlobal( QPoint_from_np(self.img_to_cli_pt(self.img_look_pt)) ))
898
self.update()
899
900
if self.drag_type == DragType.IMAGE_LOOK:
901
delta_pt = self.cli_to_img_pt(self.mouse_cli_pt) - self.cli_to_img_pt(self.drag_cli_pt)
902
self.img_look_pt = self.drag_img_look_pt - delta_pt
903
self.update()
904
905
if self.op_mode == OpMode.DRAW_PTS:
906
self.update()
907
elif self.op_mode == OpMode.EDIT_PTS:
908
if self.drag_type == DragType.POLY_PT:
909
delta_pt = self.cli_to_img_pt(self.mouse_cli_pt) - self.cli_to_img_pt(self.drag_cli_pt)
910
self.op_poly.set_point(self.drag_poly_pt_id, self.drag_poly_pt + delta_pt)
911
self.update()
912
913
def wheelEvent(self, ev):
914
super().wheelEvent(ev)
915
916
if not self.initialized:
917
return
918
919
mods = int(ev.modifiers())
920
delta = ev.angleDelta()
921
922
cli_pt = QPoint_to_np(ev.pos())
923
924
if self.drag_type == DragType.NONE:
925
sign = np.sign( delta.y() )
926
prev_img_pos = self.cli_to_img_pt (cli_pt)
927
delta_scale = sign*0.2 + sign * self.get_view_scale() / 10.0
928
self.view_scale = np.clip(self.get_view_scale() + delta_scale, 1.0, 20.0)
929
new_img_pos = self.cli_to_img_pt (cli_pt)
930
if sign > 0:
931
self.img_look_pt = self.get_img_look_pt() + (prev_img_pos-new_img_pos)#*1.5
932
else:
933
QCursor.setPos ( self.mapToGlobal(QPoint_from_np(self.img_to_cli_pt(prev_img_pos))) )
934
self.update()
935
936
def paintEvent(self, event):
937
super().paintEvent(event)
938
if not self.initialized:
939
return
940
941
qp = self.qp
942
qp.begin(self)
943
qp.setRenderHint(QPainter.Antialiasing)
944
qp.setRenderHint(QPainter.HighQualityAntialiasing)
945
qp.setRenderHint(QPainter.SmoothPixmapTransform)
946
947
src_rect = QRect(0, 0, *self.img_size)
948
dst_rect = self.img_to_cli_rect( src_rect )
949
950
if self.op_mode == OpMode.VIEW_BAKED:
951
qp.drawPixmap(dst_rect, self.img_baked_pixmap, src_rect)
952
elif self.op_mode == OpMode.VIEW_XSEG_MASK:
953
if self.xseg_mask_pixmap is not None:
954
qp.drawPixmap(dst_rect, self.xseg_mask_pixmap, src_rect)
955
else:
956
if self.cbar.btn_view_xseg_overlay_mask_act.isChecked() and \
957
self.xseg_overlay_mask_pixmap is not None:
958
qp.drawPixmap(dst_rect, self.xseg_overlay_mask_pixmap, src_rect)
959
elif self.img_pixmap is not None:
960
qp.drawPixmap(dst_rect, self.img_pixmap, src_rect)
961
962
polys = self.ie_polys.get_polys()
963
polys_len = len(polys)
964
965
color_scheme = self.get_current_color_scheme()
966
967
pt_rad = self.canvas_config.pt_radius
968
pt_rad_x2 = pt_rad*2
969
970
pt_select_radius = self.canvas_config.pt_select_radius
971
972
op_mode = self.op_mode
973
op_poly = self.op_poly
974
975
for i,poly in enumerate(polys):
976
977
selected_pt_path = QPainterPath()
978
poly_line_path = QPainterPath()
979
pts_line_path = QPainterPath()
980
981
pt_remove_cli_pt = None
982
poly_pts = poly.get_pts()
983
for pt_id, img_pt in enumerate(poly_pts):
984
cli_pt = self.img_to_cli_pt(img_pt)
985
q_cli_pt = QPoint_from_np(cli_pt)
986
987
if pt_id == 0:
988
poly_line_path.moveTo(q_cli_pt)
989
else:
990
poly_line_path.lineTo(q_cli_pt)
991
992
993
if poly == op_poly:
994
if self.op_mode == OpMode.DRAW_PTS or \
995
(self.op_mode == OpMode.EDIT_PTS and \
996
(self.pt_edit_mode == PTEditMode.MOVE) or \
997
(self.pt_edit_mode == PTEditMode.ADD_DEL and self.mouse_op_poly_pt_id == pt_id) \
998
):
999
pts_line_path.moveTo( QPoint_from_np(cli_pt + np.float32([0,-pt_rad])) )
1000
pts_line_path.lineTo( QPoint_from_np(cli_pt + np.float32([0,pt_rad])) )
1001
pts_line_path.moveTo( QPoint_from_np(cli_pt + np.float32([-pt_rad,0])) )
1002
pts_line_path.lineTo( QPoint_from_np(cli_pt + np.float32([pt_rad,0])) )
1003
1004
if (self.op_mode == OpMode.EDIT_PTS and \
1005
self.pt_edit_mode == PTEditMode.ADD_DEL and \
1006
self.mouse_op_poly_pt_id == pt_id):
1007
pt_remove_cli_pt = cli_pt
1008
1009
if self.op_mode == OpMode.DRAW_PTS and \
1010
len(op_poly.get_pts()) >= 3 and pt_id == 0 and self.mouse_op_poly_pt_id == pt_id:
1011
# Circle around poly point
1012
selected_pt_path.addEllipse(q_cli_pt, pt_rad_x2, pt_rad_x2)
1013
1014
1015
if poly == op_poly:
1016
if op_mode == OpMode.DRAW_PTS:
1017
# Line from last point to mouse
1018
poly_line_path.lineTo( QPoint_from_np(self.mouse_cli_pt) )
1019
1020
if self.mouse_op_poly_pt_id is not None:
1021
pass
1022
1023
if self.mouse_op_poly_edge_id_pt is not None:
1024
if self.pt_edit_mode == PTEditMode.ADD_DEL and self.mouse_op_poly_pt_id is None:
1025
# Ready to insert point on edge
1026
m_cli_pt = self.mouse_op_poly_edge_id_pt
1027
pts_line_path.moveTo( QPoint_from_np(m_cli_pt + np.float32([0,-pt_rad])) )
1028
pts_line_path.lineTo( QPoint_from_np(m_cli_pt + np.float32([0,pt_rad])) )
1029
pts_line_path.moveTo( QPoint_from_np(m_cli_pt + np.float32([-pt_rad,0])) )
1030
pts_line_path.lineTo( QPoint_from_np(m_cli_pt + np.float32([pt_rad,0])) )
1031
1032
if len(poly_pts) >= 2:
1033
# Closing poly line
1034
poly_line_path.lineTo( QPoint_from_np(self.img_to_cli_pt(poly_pts[0])) )
1035
1036
# Draw calls
1037
qp.setPen(color_scheme.pt_outline_pen)
1038
qp.setBrush(QBrush())
1039
qp.drawPath(selected_pt_path)
1040
1041
qp.setPen(color_scheme.poly_outline_solid_pen)
1042
qp.setBrush(QBrush())
1043
qp.drawPath(pts_line_path)
1044
1045
if poly.get_type() == SegIEPolyType.INCLUDE:
1046
qp.setPen(color_scheme.poly_outline_solid_pen)
1047
else:
1048
qp.setPen(color_scheme.poly_outline_dot_pen)
1049
1050
qp.setBrush(color_scheme.poly_unselected_brush)
1051
if op_mode == OpMode.NONE:
1052
if poly == self.mouse_wire_poly:
1053
qp.setBrush(color_scheme.poly_selected_brush)
1054
#else:
1055
# if poly == op_poly:
1056
# qp.setBrush(color_scheme.poly_selected_brush)
1057
1058
qp.drawPath(poly_line_path)
1059
1060
if pt_remove_cli_pt is not None:
1061
qp.setPen(color_scheme.poly_outline_solid_pen)
1062
qp.setBrush(QBrush())
1063
1064
qp.drawLine( *(pt_remove_cli_pt + np.float32([-pt_rad_x2,-pt_rad_x2])), *(pt_remove_cli_pt + np.float32([pt_rad_x2,pt_rad_x2])) )
1065
qp.drawLine( *(pt_remove_cli_pt + np.float32([-pt_rad_x2,pt_rad_x2])), *(pt_remove_cli_pt + np.float32([pt_rad_x2,-pt_rad_x2])) )
1066
1067
qp.end()
1068
1069
class QCanvas(QFrame):
1070
def __init__(self):
1071
super().__init__()
1072
1073
self.canvas_control_left_bar = QCanvasControlsLeftBar()
1074
self.canvas_control_right_bar = QCanvasControlsRightBar()
1075
1076
cbar = sn( btn_poly_color_red_act = self.canvas_control_right_bar.btn_poly_color_red_act,
1077
btn_poly_color_green_act = self.canvas_control_right_bar.btn_poly_color_green_act,
1078
btn_poly_color_blue_act = self.canvas_control_right_bar.btn_poly_color_blue_act,
1079
btn_view_baked_mask_act = self.canvas_control_right_bar.btn_view_baked_mask_act,
1080
btn_view_xseg_mask_act = self.canvas_control_right_bar.btn_view_xseg_mask_act,
1081
btn_view_xseg_overlay_mask_act = self.canvas_control_right_bar.btn_view_xseg_overlay_mask_act,
1082
btn_poly_color_act_grp = self.canvas_control_right_bar.btn_poly_color_act_grp,
1083
btn_view_lock_center_act = self.canvas_control_right_bar.btn_view_lock_center_act,
1084
1085
btn_poly_type_include_act = self.canvas_control_left_bar.btn_poly_type_include_act,
1086
btn_poly_type_exclude_act = self.canvas_control_left_bar.btn_poly_type_exclude_act,
1087
btn_poly_type_act_grp = self.canvas_control_left_bar.btn_poly_type_act_grp,
1088
btn_undo_pt_act = self.canvas_control_left_bar.btn_undo_pt_act,
1089
btn_redo_pt_act = self.canvas_control_left_bar.btn_redo_pt_act,
1090
btn_delete_poly_act = self.canvas_control_left_bar.btn_delete_poly_act,
1091
btn_pt_edit_mode_act = self.canvas_control_left_bar.btn_pt_edit_mode_act )
1092
1093
self.op = QCanvasOperator(cbar)
1094
self.l = QHBoxLayout()
1095
self.l.setContentsMargins(0,0,0,0)
1096
self.l.addWidget(self.canvas_control_left_bar)
1097
self.l.addWidget(self.op)
1098
self.l.addWidget(self.canvas_control_right_bar)
1099
self.setLayout(self.l)
1100
1101
class LoaderQSubprocessor(QSubprocessor):
1102
def __init__(self, image_paths, q_label, q_progressbar, on_finish_func ):
1103
1104
self.image_paths = image_paths
1105
self.image_paths_len = len(image_paths)
1106
self.idxs = [*range(self.image_paths_len)]
1107
1108
self.filtered_image_paths = self.image_paths.copy()
1109
1110
self.image_paths_has_ie_polys = { image_path : False for image_path in self.image_paths }
1111
1112
self.q_label = q_label
1113
self.q_progressbar = q_progressbar
1114
self.q_progressbar.setRange(0, self.image_paths_len)
1115
self.q_progressbar.setValue(0)
1116
self.q_progressbar.update()
1117
self.on_finish_func = on_finish_func
1118
self.done_count = 0
1119
super().__init__('LoaderQSubprocessor', LoaderQSubprocessor.Cli, 60)
1120
1121
def get_data(self, host_dict):
1122
if len (self.idxs) > 0:
1123
idx = self.idxs.pop(0)
1124
image_path = self.image_paths[idx]
1125
self.q_label.setText(f'{QStringDB.loading_tip}... {image_path.name}')
1126
1127
return idx, image_path
1128
1129
return None
1130
1131
def on_clients_finalized(self):
1132
self.on_finish_func([x for x in self.filtered_image_paths if x is not None], self.image_paths_has_ie_polys)
1133
1134
def on_data_return (self, host_dict, data):
1135
self.idxs.insert(0, data[0])
1136
1137
def on_result (self, host_dict, data, result):
1138
idx, has_dflimg, has_ie_polys = result
1139
1140
if not has_dflimg:
1141
self.filtered_image_paths[idx] = None
1142
self.image_paths_has_ie_polys[self.image_paths[idx]] = has_ie_polys
1143
1144
self.done_count += 1
1145
if self.q_progressbar is not None:
1146
self.q_progressbar.setValue(self.done_count)
1147
1148
class Cli(QSubprocessor.Cli):
1149
def process_data(self, data):
1150
idx, filename = data
1151
dflimg = DFLIMG.load(filename)
1152
if dflimg is not None and dflimg.has_data():
1153
ie_polys = dflimg.get_seg_ie_polys()
1154
1155
return idx, True, ie_polys.has_polys()
1156
return idx, False, False
1157
1158
class MainWindow(QXMainWindow):
1159
1160
def __init__(self, input_dirpath, cfg_root_path):
1161
self.loading_frame = None
1162
self.help_frame = None
1163
1164
super().__init__()
1165
1166
self.input_dirpath = input_dirpath
1167
self.trash_dirpath = input_dirpath.parent / (input_dirpath.name + '_trash')
1168
self.cfg_root_path = cfg_root_path
1169
1170
self.cfg_path = cfg_root_path / 'MainWindow_cfg.dat'
1171
self.cfg_dict = pickle.loads(self.cfg_path.read_bytes()) if self.cfg_path.exists() else {}
1172
1173
self.cached_images = {}
1174
self.cached_has_ie_polys = {}
1175
1176
self.initialize_ui()
1177
1178
# Loader
1179
self.loading_frame = QFrame(self.main_canvas_frame)
1180
self.loading_frame.setAutoFillBackground(True)
1181
self.loading_frame.setFrameShape(QFrame.StyledPanel)
1182
self.loader_label = QLabel()
1183
self.loader_progress_bar = QProgressBar()
1184
1185
intro_image = QLabel()
1186
intro_image.setPixmap( QPixmap.fromImage(QImageDB.intro) )
1187
1188
intro_image_frame_l = QVBoxLayout()
1189
intro_image_frame_l.addWidget(intro_image, alignment=Qt.AlignCenter)
1190
intro_image_frame = QFrame()
1191
intro_image_frame.setSizePolicy (QSizePolicy.Expanding, QSizePolicy.Expanding)
1192
intro_image_frame.setLayout(intro_image_frame_l)
1193
1194
loading_frame_l = QVBoxLayout()
1195
loading_frame_l.addWidget (intro_image_frame)
1196
loading_frame_l.addWidget (self.loader_label)
1197
loading_frame_l.addWidget (self.loader_progress_bar)
1198
self.loading_frame.setLayout(loading_frame_l)
1199
1200
self.loader_subprocessor = LoaderQSubprocessor( image_paths=pathex.get_image_paths(input_dirpath, return_Path_class=True),
1201
q_label=self.loader_label,
1202
q_progressbar=self.loader_progress_bar,
1203
on_finish_func=self.on_loader_finish )
1204
1205
1206
def on_loader_finish(self, image_paths, image_paths_has_ie_polys):
1207
self.image_paths_done = []
1208
self.image_paths = image_paths
1209
self.image_paths_has_ie_polys = image_paths_has_ie_polys
1210
self.set_has_ie_polys_count ( len([ 1 for x in self.image_paths_has_ie_polys if self.image_paths_has_ie_polys[x] == True]) )
1211
self.loading_frame.hide()
1212
self.loading_frame = None
1213
1214
self.process_next_image(first_initialization=True)
1215
1216
def closeEvent(self, ev):
1217
self.cfg_dict['geometry'] = self.saveGeometry().data()
1218
self.cfg_path.write_bytes( pickle.dumps(self.cfg_dict) )
1219
1220
1221
def update_cached_images (self, count=5):
1222
d = self.cached_images
1223
1224
for image_path in self.image_paths_done[:-count]+self.image_paths[count:]:
1225
if image_path in d:
1226
del d[image_path]
1227
1228
for image_path in self.image_paths[:count]+self.image_paths_done[-count:]:
1229
if image_path not in d:
1230
img = cv2_imread(image_path)
1231
if img is not None:
1232
d[image_path] = img
1233
1234
def load_image(self, image_path):
1235
try:
1236
img = self.cached_images.get(image_path, None)
1237
if img is None:
1238
img = cv2_imread(image_path)
1239
self.cached_images[image_path] = img
1240
if img is None:
1241
io.log_err(f'Unable to load {image_path}')
1242
except:
1243
img = None
1244
1245
return img
1246
1247
def update_preview_bar(self):
1248
count = self.image_bar.get_preview_images_count()
1249
d = self.cached_images
1250
prev_imgs = [ d.get(image_path, None) for image_path in self.image_paths_done[-1:-count:-1] ]
1251
next_imgs = [ d.get(image_path, None) for image_path in self.image_paths[:count] ]
1252
self.image_bar.update_images(prev_imgs, next_imgs)
1253
1254
1255
def canvas_initialize(self, image_path, only_has_polys=False):
1256
if only_has_polys and not self.image_paths_has_ie_polys[image_path]:
1257
return False
1258
1259
dflimg = DFLIMG.load(image_path)
1260
if not dflimg or not dflimg.has_data():
1261
return False
1262
1263
ie_polys = dflimg.get_seg_ie_polys()
1264
xseg_mask = dflimg.get_xseg_mask()
1265
img = self.load_image(image_path)
1266
if img is None:
1267
return False
1268
1269
self.canvas.op.initialize ( img, ie_polys=ie_polys, xseg_mask=xseg_mask )
1270
1271
self.filename_label.setText(f"{image_path.name}")
1272
1273
return True
1274
1275
def canvas_finalize(self, image_path):
1276
self.canvas.op.finalize()
1277
1278
if image_path.exists():
1279
dflimg = DFLIMG.load(image_path)
1280
ie_polys = dflimg.get_seg_ie_polys()
1281
new_ie_polys = self.canvas.op.get_ie_polys()
1282
1283
if not new_ie_polys.identical(ie_polys):
1284
prev_has_polys = self.image_paths_has_ie_polys[image_path]
1285
self.image_paths_has_ie_polys[image_path] = new_ie_polys.has_polys()
1286
new_has_polys = self.image_paths_has_ie_polys[image_path]
1287
1288
if not prev_has_polys and new_has_polys:
1289
self.set_has_ie_polys_count ( self.get_has_ie_polys_count() +1)
1290
elif prev_has_polys and not new_has_polys:
1291
self.set_has_ie_polys_count ( self.get_has_ie_polys_count() -1)
1292
1293
dflimg.set_seg_ie_polys( new_ie_polys )
1294
dflimg.save()
1295
1296
self.filename_label.setText(f"")
1297
1298
def process_prev_image(self):
1299
key_mods = QApplication.keyboardModifiers()
1300
step = 5 if key_mods == Qt.ShiftModifier else 1
1301
only_has_polys = key_mods == Qt.ControlModifier
1302
1303
if self.canvas.op.is_initialized():
1304
self.canvas_finalize(self.image_paths[0])
1305
1306
while True:
1307
for _ in range(step):
1308
if len(self.image_paths_done) != 0:
1309
self.image_paths.insert (0, self.image_paths_done.pop(-1))
1310
else:
1311
break
1312
if len(self.image_paths) == 0:
1313
break
1314
1315
ret = self.canvas_initialize(self.image_paths[0], len(self.image_paths_done) != 0 and only_has_polys)
1316
1317
if ret or len(self.image_paths_done) == 0:
1318
break
1319
1320
self.update_cached_images()
1321
self.update_preview_bar()
1322
1323
def process_next_image(self, first_initialization=False):
1324
key_mods = QApplication.keyboardModifiers()
1325
1326
step = 0 if first_initialization else 5 if key_mods == Qt.ShiftModifier else 1
1327
only_has_polys = False if first_initialization else key_mods == Qt.ControlModifier
1328
1329
if self.canvas.op.is_initialized():
1330
self.canvas_finalize(self.image_paths[0])
1331
1332
while True:
1333
for _ in range(step):
1334
if len(self.image_paths) != 0:
1335
self.image_paths_done.append(self.image_paths.pop(0))
1336
else:
1337
break
1338
if len(self.image_paths) == 0:
1339
break
1340
if self.canvas_initialize(self.image_paths[0], only_has_polys):
1341
break
1342
1343
self.update_cached_images()
1344
self.update_preview_bar()
1345
1346
def trash_current_image(self):
1347
self.process_next_image()
1348
1349
img_path = self.image_paths_done.pop(-1)
1350
img_path = Path(img_path)
1351
self.trash_dirpath.mkdir(parents=True, exist_ok=True)
1352
img_path.rename( self.trash_dirpath / img_path.name )
1353
1354
self.update_cached_images()
1355
self.update_preview_bar()
1356
1357
def initialize_ui(self):
1358
1359
self.canvas = QCanvas()
1360
1361
image_bar = self.image_bar = ImagePreviewSequenceBar(preview_images_count=9, icon_size=QUIConfig.preview_bar_icon_q_size.width())
1362
image_bar.setSizePolicy ( QSizePolicy.Fixed, QSizePolicy.Fixed )
1363
1364
1365
btn_prev_image = QXIconButton(QIconDB.left, QStringDB.btn_prev_image_tip, shortcut='A', click_func=self.process_prev_image)
1366
btn_prev_image.setIconSize(QUIConfig.preview_bar_icon_q_size)
1367
1368
btn_next_image = QXIconButton(QIconDB.right, QStringDB.btn_next_image_tip, shortcut='D', click_func=self.process_next_image)
1369
btn_next_image.setIconSize(QUIConfig.preview_bar_icon_q_size)
1370
1371
btn_delete_image = QXIconButton(QIconDB.trashcan, QStringDB.btn_delete_image_tip, shortcut='X', click_func=self.trash_current_image)
1372
btn_delete_image.setIconSize(QUIConfig.preview_bar_icon_q_size)
1373
1374
pad_image = QWidget()
1375
pad_image.setFixedSize(QUIConfig.preview_bar_icon_q_size)
1376
1377
preview_image_bar_frame_l = QHBoxLayout()
1378
preview_image_bar_frame_l.setContentsMargins(0,0,0,0)
1379
preview_image_bar_frame_l.addWidget ( pad_image, alignment=Qt.AlignCenter)
1380
preview_image_bar_frame_l.addWidget ( btn_prev_image, alignment=Qt.AlignCenter)
1381
preview_image_bar_frame_l.addWidget ( image_bar)
1382
preview_image_bar_frame_l.addWidget ( btn_next_image, alignment=Qt.AlignCenter)
1383
#preview_image_bar_frame_l.addWidget ( btn_delete_image, alignment=Qt.AlignCenter)
1384
1385
preview_image_bar_frame = QFrame()
1386
preview_image_bar_frame.setSizePolicy ( QSizePolicy.Fixed, QSizePolicy.Fixed )
1387
preview_image_bar_frame.setLayout(preview_image_bar_frame_l)
1388
1389
preview_image_bar_frame2_l = QHBoxLayout()
1390
preview_image_bar_frame2_l.setContentsMargins(0,0,0,0)
1391
preview_image_bar_frame2_l.addWidget ( btn_delete_image, alignment=Qt.AlignCenter)
1392
1393
preview_image_bar_frame2 = QFrame()
1394
preview_image_bar_frame2.setSizePolicy ( QSizePolicy.Fixed, QSizePolicy.Fixed )
1395
preview_image_bar_frame2.setLayout(preview_image_bar_frame2_l)
1396
1397
preview_image_bar_l = QHBoxLayout()
1398
preview_image_bar_l.addWidget (preview_image_bar_frame, alignment=Qt.AlignCenter)
1399
preview_image_bar_l.addWidget (preview_image_bar_frame2)
1400
1401
preview_image_bar = QFrame()
1402
preview_image_bar.setFrameShape(QFrame.StyledPanel)
1403
preview_image_bar.setSizePolicy ( QSizePolicy.Expanding, QSizePolicy.Fixed )
1404
preview_image_bar.setLayout(preview_image_bar_l)
1405
1406
label_font = QFont('Courier New')
1407
self.filename_label = QLabel()
1408
self.filename_label.setFont(label_font)
1409
1410
self.has_ie_polys_count_label = QLabel()
1411
1412
status_frame_l = QHBoxLayout()
1413
status_frame_l.setContentsMargins(0,0,0,0)
1414
status_frame_l.addWidget ( QLabel(), alignment=Qt.AlignCenter)
1415
status_frame_l.addWidget (self.filename_label, alignment=Qt.AlignCenter)
1416
status_frame_l.addWidget (self.has_ie_polys_count_label, alignment=Qt.AlignCenter)
1417
status_frame = QFrame()
1418
status_frame.setLayout(status_frame_l)
1419
1420
main_canvas_l = QVBoxLayout()
1421
main_canvas_l.setContentsMargins(0,0,0,0)
1422
main_canvas_l.addWidget (self.canvas)
1423
main_canvas_l.addWidget (status_frame)
1424
main_canvas_l.addWidget (preview_image_bar)
1425
1426
self.main_canvas_frame = QFrame()
1427
self.main_canvas_frame.setLayout(main_canvas_l)
1428
1429
self.main_l = QHBoxLayout()
1430
self.main_l.setContentsMargins(0,0,0,0)
1431
self.main_l.addWidget (self.main_canvas_frame)
1432
1433
self.setLayout(self.main_l)
1434
1435
geometry = self.cfg_dict.get('geometry', None)
1436
if geometry is not None:
1437
self.restoreGeometry(geometry)
1438
else:
1439
self.move( QPoint(0,0))
1440
1441
def get_has_ie_polys_count(self):
1442
return self.has_ie_polys_count
1443
1444
def set_has_ie_polys_count(self, c):
1445
self.has_ie_polys_count = c
1446
self.has_ie_polys_count_label.setText(f"{c} {QStringDB.labeled_tip}")
1447
1448
def resizeEvent(self, ev):
1449
if self.loading_frame is not None:
1450
self.loading_frame.resize( ev.size() )
1451
if self.help_frame is not None:
1452
self.help_frame.resize( ev.size() )
1453
1454
def start(input_dirpath):
1455
"""
1456
returns exit_code
1457
"""
1458
io.log_info("Running XSeg editor.")
1459
1460
if PackedFaceset.path_contains(input_dirpath):
1461
io.log_info (f'\n{input_dirpath} contains packed faceset! Unpack it first.\n')
1462
return 1
1463
1464
root_path = Path(__file__).parent
1465
cfg_root_path = Path(tempfile.gettempdir())
1466
1467
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
1468
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
1469
1470
app = QApplication([])
1471
app.setApplicationName("XSegEditor")
1472
app.setStyle('Fusion')
1473
1474
QFontDatabase.addApplicationFont( str(root_path / 'gfx' / 'fonts' / 'NotoSans-Medium.ttf') )
1475
1476
app.setFont( QFont('NotoSans'))
1477
1478
QUIConfig.initialize()
1479
QStringDB.initialize()
1480
1481
QIconDB.initialize( root_path / 'gfx' / 'icons' )
1482
QCursorDB.initialize( root_path / 'gfx' / 'cursors' )
1483
QImageDB.initialize( root_path / 'gfx' / 'images' )
1484
1485
app.setWindowIcon(QIconDB.app_icon)
1486
app.setPalette( QDarkPalette() )
1487
1488
win = MainWindow( input_dirpath=input_dirpath, cfg_root_path=cfg_root_path)
1489
1490
win.show()
1491
win.raise_()
1492
1493
app.exec_()
1494
return 0
1495
1496