Add scrollwheel support and arrow key support from switch back and forth between images and add a option with button that switches it to a mode where I can import alpha masks and have then go on the corresponding images and let me move and resize the mask so they match and also let me add all or just for the current image and follow the codes format here is code import pickle
import tempfile
import time
from enum import IntEnum
from types import SimpleNamespace as sn

import cv2
import numpy as np
import numpy.linalg as npla
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

from core import imagelib, pathex
from core.cv2ex import *
from core.imagelib import SegIEPolys, SegIEPolyType, sd
from core.qtex import *
from DFLIMG import *
from samplelib import PackedFaceset

from .QCursorDB import QCursorDB
from .QIconDB import QIconDB
from .QStringDB import QStringDB
from .QImageDB import QImageDB

class OpMode(IntEnum):
    NONE = 0
    DRAW_PTS = 1
    EDIT_PTS = 2
    VIEW_BAKED = 3
    VIEW_XSEG_MASK = 4

class PTEditMode(IntEnum):
    MOVE = 0
    ADD_DEL = 1

class DragType(IntEnum):
    NONE = 0
    IMAGE_LOOK = 1
    POLY_PT = 2

class ViewLock(IntEnum):
    NONE = 0
    CENTER = 1

class QUIConfig():
    @staticmethod
    def initialize(icon_size = 48, icon_spacer_size=16, preview_bar_icon_size=64):
        QUIConfig.icon_q_size = QSize(icon_size, icon_size)
        QUIConfig.icon_spacer_q_size = QSize(icon_spacer_size, icon_spacer_size)
        QUIConfig.preview_bar_icon_q_size = QSize(preview_bar_icon_size, preview_bar_icon_size)

class ImagePreviewSequenceBar(QFrame):
    def __init__(self, preview_images_count, icon_size):
        super().__init__()
        self.preview_images_count = preview_images_count = max(1, preview_images_count + (preview_images_count % 2 -1) )

        self.icon_size = icon_size

        black_q_img = QImage(np.zeros( (icon_size,icon_size,3) ).data, icon_size, icon_size, 3*icon_size, QImage.Format_RGB888)
        self.black_q_pixmap = QPixmap.fromImage(black_q_img)

        self.image_containers = [ QLabel() for i in range(preview_images_count)]

        main_frame_l_cont_hl = QGridLayout()
        main_frame_l_cont_hl.setContentsMargins(0,0,0,0)
        #main_frame_l_cont_hl.setSpacing(0)



        for i in range(len(self.image_containers)):
            q_label = self.image_containers[i]
            q_label.setScaledContents(True)
            if i == preview_images_count//2:
                q_label.setMinimumSize(icon_size+16, icon_size+16 )
                q_label.setMaximumSize(icon_size+16, icon_size+16 )
            else:
                q_label.setMinimumSize(icon_size, icon_size )
                q_label.setMaximumSize(icon_size, icon_size )
                opacity_effect = QGraphicsOpacityEffect()
                opacity_effect.setOpacity(0.5)
                q_label.setGraphicsEffect(opacity_effect)

            q_label.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)

            main_frame_l_cont_hl.addWidget (q_label, 0, i)

        self.setLayout(main_frame_l_cont_hl)

        self.prev_img_conts = self.image_containers[(preview_images_count//2) -1::-1]
        self.next_img_conts = self.image_containers[preview_images_count//2:]

        self.update_images()

    def get_preview_images_count(self):
        return self.preview_images_count

    def update_images(self, prev_imgs=None, next_imgs=None):
        # Fix arrays
        if prev_imgs is None:
            prev_imgs = []
        prev_img_conts_len = len(self.prev_img_conts)
        prev_q_imgs_len = len(prev_imgs)
        if prev_q_imgs_len < prev_img_conts_len:
            for i in range ( prev_img_conts_len - prev_q_imgs_len ):
                prev_imgs.append(None)
        elif prev_q_imgs_len > prev_img_conts_len:
            prev_imgs = prev_imgs[:prev_img_conts_len]

        if next_imgs is None:
            next_imgs = []
        next_img_conts_len = len(self.next_img_conts)
        next_q_imgs_len = len(next_imgs)
        if next_q_imgs_len < next_img_conts_len:
            for i in range ( next_img_conts_len - next_q_imgs_len ):
                next_imgs.append(None)
        elif next_q_imgs_len > next_img_conts_len:
            next_imgs = next_imgs[:next_img_conts_len]

        for i,img in enumerate(prev_imgs):
            self.prev_img_conts[i].setPixmap( QPixmap.fromImage( QImage_from_np(img) ) if img is not None else self.black_q_pixmap )

        for i,img in enumerate(next_imgs):
            self.next_img_conts[i].setPixmap( QPixmap.fromImage( QImage_from_np(img) ) if img is not None else self.black_q_pixmap )
class ColorScheme():
    def __init__(self, unselected_color, selected_color, outline_color, outline_width, pt_outline_color, cross_cursor):
        self.poly_unselected_brush = QBrush(unselected_color)
        self.poly_selected_brush = QBrush(selected_color)

        self.poly_outline_solid_pen = QPen(outline_color, outline_width, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        self.poly_outline_dot_pen = QPen(outline_color, outline_width, Qt.DotLine, Qt.RoundCap, Qt.RoundJoin)

        self.pt_outline_pen = QPen(pt_outline_color)
        self.cross_cursor = cross_cursor

class CanvasConfig():

    def __init__(self,
                 pt_radius=4,
                 pt_select_radius=8,
                 color_schemes=None,
                 **kwargs):
        self.pt_radius = pt_radius
        self.pt_select_radius = pt_select_radius

        if color_schemes is None:
            color_schemes = [
                    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 ),
                    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 ),
                    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 ),
                    ]
        self.color_schemes = color_schemes

class QCanvasControlsLeftBar(QFrame):

    def __init__(self):
        super().__init__()
        #==============================================
        btn_poly_type_include = QToolButton()
        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)
        btn_poly_type_include.setDefaultAction(self.btn_poly_type_include_act)
        btn_poly_type_include.setIconSize(QUIConfig.icon_q_size)

        btn_poly_type_exclude = QToolButton()
        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)
        btn_poly_type_exclude.setDefaultAction(self.btn_poly_type_exclude_act)
        btn_poly_type_exclude.setIconSize(QUIConfig.icon_q_size)

        self.btn_poly_type_act_grp = QActionGroup (self)
        self.btn_poly_type_act_grp.addAction(self.btn_poly_type_include_act)
        self.btn_poly_type_act_grp.addAction(self.btn_poly_type_exclude_act)
        self.btn_poly_type_act_grp.setExclusive(True)
        #==============================================
        btn_undo_pt = QToolButton()
        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)
        btn_undo_pt.setDefaultAction(self.btn_undo_pt_act)
        btn_undo_pt.setIconSize(QUIConfig.icon_q_size)

        btn_redo_pt = QToolButton()
        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)
        btn_redo_pt.setDefaultAction(self.btn_redo_pt_act)
        btn_redo_pt.setIconSize(QUIConfig.icon_q_size)

        btn_delete_poly = QToolButton()
        self.btn_delete_poly_act = QActionEx( QIconDB.delete_poly, QStringDB.btn_delete_poly_tip, shortcut='Delete', shortcut_in_tooltip=True)
        btn_delete_poly.setDefaultAction(self.btn_delete_poly_act)
        btn_delete_poly.setIconSize(QUIConfig.icon_q_size)
        #==============================================
        btn_pt_edit_mode = QToolButton()
        self.btn_pt_edit_mode_act = QActionEx( QIconDB.pt_edit_mode, QStringDB.btn_pt_edit_mode_tip, shortcut_in_tooltip=True, is_checkable=True)
        btn_pt_edit_mode.setDefaultAction(self.btn_pt_edit_mode_act)
        btn_pt_edit_mode.setIconSize(QUIConfig.icon_q_size)
        #==============================================

        controls_bar_frame2_l = QVBoxLayout()
        controls_bar_frame2_l.addWidget ( btn_poly_type_include )
        controls_bar_frame2_l.addWidget ( btn_poly_type_exclude )
        controls_bar_frame2 = QFrame()
        controls_bar_frame2.setFrameShape(QFrame.StyledPanel)
        controls_bar_frame2.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
        controls_bar_frame2.setLayout(controls_bar_frame2_l)

        controls_bar_frame3_l = QVBoxLayout()
        controls_bar_frame3_l.addWidget ( btn_undo_pt )
        controls_bar_frame3_l.addWidget ( btn_redo_pt )
        controls_bar_frame3_l.addWidget ( btn_delete_poly )
        controls_bar_frame3 = QFrame()
        controls_bar_frame3.setFrameShape(QFrame.StyledPanel)
        controls_bar_frame3.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
        controls_bar_frame3.setLayout(controls_bar_frame3_l)

        controls_bar_frame4_l = QVBoxLayout()
        controls_bar_frame4_l.addWidget ( btn_pt_edit_mode )
        controls_bar_frame4 = QFrame()
        controls_bar_frame4.setFrameShape(QFrame.StyledPanel)
        controls_bar_frame4.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
        controls_bar_frame4.setLayout(controls_bar_frame4_l)

        controls_bar_l = QVBoxLayout()
        controls_bar_l.setContentsMargins(0,0,0,0)
        controls_bar_l.addWidget(controls_bar_frame2)
        controls_bar_l.addWidget(controls_bar_frame3)
        controls_bar_l.addWidget(controls_bar_frame4)

        self.setSizePolicy ( QSizePolicy.Fixed, QSizePolicy.Expanding )
        self.setLayout(controls_bar_l)
class QCanvasControlsRightBar(QFrame):

    def __init__(self):
        super().__init__()
        #==============================================
        btn_poly_color_red = QToolButton()
        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)
        btn_poly_color_red.setDefaultAction(self.btn_poly_color_red_act)
        btn_poly_color_red.setIconSize(QUIConfig.icon_q_size)

        btn_poly_color_green = QToolButton()
        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)
        btn_poly_color_green.setDefaultAction(self.btn_poly_color_green_act)
        btn_poly_color_green.setIconSize(QUIConfig.icon_q_size)

        btn_poly_color_blue = QToolButton()
        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)
        btn_poly_color_blue.setDefaultAction(self.btn_poly_color_blue_act)
        btn_poly_color_blue.setIconSize(QUIConfig.icon_q_size)

        btn_view_baked_mask = QToolButton()
        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)
        btn_view_baked_mask.setDefaultAction(self.btn_view_baked_mask_act)
        btn_view_baked_mask.setIconSize(QUIConfig.icon_q_size)

        btn_view_xseg_mask = QToolButton()
        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)
        btn_view_xseg_mask.setDefaultAction(self.btn_view_xseg_mask_act)
        btn_view_xseg_mask.setIconSize(QUIConfig.icon_q_size)

        btn_view_xseg_overlay_mask = QToolButton()
        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)
        btn_view_xseg_overlay_mask.setDefaultAction(self.btn_view_xseg_overlay_mask_act)
        btn_view_xseg_overlay_mask.setIconSize(QUIConfig.icon_q_size)

        self.btn_poly_color_act_grp = QActionGroup (self)
        self.btn_poly_color_act_grp.addAction(self.btn_poly_color_red_act)
        self.btn_poly_color_act_grp.addAction(self.btn_poly_color_green_act)
        self.btn_poly_color_act_grp.addAction(self.btn_poly_color_blue_act)
        self.btn_poly_color_act_grp.addAction(self.btn_view_baked_mask_act)
        self.btn_poly_color_act_grp.addAction(self.btn_view_xseg_mask_act)
        self.btn_poly_color_act_grp.setExclusive(True)
        #==============================================
        btn_view_lock_center = QToolButton()
        self.btn_view_lock_center_act = QActionEx( QIconDB.view_lock_center, QStringDB.btn_view_lock_center_tip, shortcut_in_tooltip=True, is_checkable=True)
        btn_view_lock_center.setDefaultAction(self.btn_view_lock_center_act)
        btn_view_lock_center.setIconSize(QUIConfig.icon_q_size)

        controls_bar_frame2_l = QVBoxLayout()
        controls_bar_frame2_l.addWidget ( btn_view_xseg_overlay_mask )
        controls_bar_frame2 = QFrame()
        controls_bar_frame2.setFrameShape(QFrame.StyledPanel)
        controls_bar_frame2.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
        controls_bar_frame2.setLayout(controls_bar_frame2_l)
        
        controls_bar_frame1_l = QVBoxLayout()
        controls_bar_frame1_l.addWidget ( btn_poly_color_red )
        controls_bar_frame1_l.addWidget ( btn_poly_color_green )
        controls_bar_frame1_l.addWidget ( btn_poly_color_blue )
        controls_bar_frame1_l.addWidget ( btn_view_baked_mask )
        controls_bar_frame1_l.addWidget ( btn_view_xseg_mask )
        controls_bar_frame1 = QFrame()
        controls_bar_frame1.setFrameShape(QFrame.StyledPanel)
        controls_bar_frame1.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
        controls_bar_frame1.setLayout(controls_bar_frame1_l)
        
        controls_bar_frame3_l = QVBoxLayout()
        controls_bar_frame3_l.addWidget ( btn_view_lock_center )
        controls_bar_frame3 = QFrame()
        controls_bar_frame3.setFrameShape(QFrame.StyledPanel)
        controls_bar_frame3.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed)
        controls_bar_frame3.setLayout(controls_bar_frame3_l)

        controls_bar_l = QVBoxLayout()
        controls_bar_l.setContentsMargins(0,0,0,0)
        controls_bar_l.addWidget(controls_bar_frame2)
        controls_bar_l.addWidget(controls_bar_frame1)
        controls_bar_l.addWidget(controls_bar_frame3)
        
        self.setSizePolicy ( QSizePolicy.Fixed, QSizePolicy.Expanding )
        self.setLayout(controls_bar_l)

class QCanvasOperator(QWidget):
    def __init__(self, cbar):
        super().__init__()
        self.cbar = cbar

        self.set_cbar_disabled()

        self.cbar.btn_poly_color_red_act.triggered.connect ( lambda : self.set_color_scheme_id(0) )
        self.cbar.btn_poly_color_green_act.triggered.connect ( lambda : self.set_color_scheme_id(1) )
        self.cbar.btn_poly_color_blue_act.triggered.connect ( lambda : self.set_color_scheme_id(2) )
        self.cbar.btn_view_baked_mask_act.triggered.connect ( lambda : self.set_op_mode(OpMode.VIEW_BAKED) )
        self.cbar.btn_view_xseg_mask_act.triggered.connect ( lambda : self.set_op_mode(OpMode.VIEW_XSEG_MASK) )

        self.cbar.btn_view_xseg_overlay_mask_act.toggled.connect ( lambda is_checked: self.update() )

        self.cbar.btn_poly_type_include_act.triggered.connect ( lambda : self.set_poly_include_type(SegIEPolyType.INCLUDE) )
        self.cbar.btn_poly_type_exclude_act.triggered.connect ( lambda : self.set_poly_include_type(SegIEPolyType.EXCLUDE) )

        self.cbar.btn_undo_pt_act.triggered.connect ( lambda : self.action_undo_pt() )
        self.cbar.btn_redo_pt_act.triggered.connect ( lambda : self.action_redo_pt() )

        self.cbar.btn_delete_poly_act.triggered.connect ( lambda : self.action_delete_poly() )

        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 ) )
        self.cbar.btn_view_lock_center_act.toggled.connect ( lambda is_checked: self.set_view_lock( ViewLock.CENTER if is_checked else ViewLock.NONE ) )

        self.mouse_in_widget = False

        QXMainWindow.inst.add_keyPressEvent_listener ( self.on_keyPressEvent )
        QXMainWindow.inst.add_keyReleaseEvent_listener ( self.on_keyReleaseEvent )

        self.qp = QPainter()
        self.initialized = False
        self.last_state = None

    def initialize(self, img, img_look_pt=None, view_scale=None, ie_polys=None, xseg_mask=None, canvas_config=None ):
        q_img = self.q_img = QImage_from_np(img)
        self.img_pixmap = QPixmap.fromImage(q_img)

        self.xseg_mask_pixmap = None
        self.xseg_overlay_mask_pixmap = None
        if xseg_mask is not None:
            h,w,c = img.shape
            xseg_mask = cv2.resize(xseg_mask, (w,h), interpolation=cv2.INTER_CUBIC)
            xseg_mask = imagelib.normalize_channels(xseg_mask, 1)
            xseg_img = img.astype(np.float32)/255.0
            xseg_overlay_mask = xseg_img*(1-xseg_mask)*0.5 + xseg_img*xseg_mask
            xseg_overlay_mask = np.clip(xseg_overlay_mask*255, 0, 255).astype(np.uint8)
            xseg_mask = np.clip(xseg_mask*255, 0, 255).astype(np.uint8)
            self.xseg_mask_pixmap = QPixmap.fromImage(QImage_from_np(xseg_mask))
            self.xseg_overlay_mask_pixmap = QPixmap.fromImage(QImage_from_np(xseg_overlay_mask))

        self.img_size = QSize_to_np (self.img_pixmap.size())

        self.img_look_pt = img_look_pt
        self.view_scale = view_scale

        if ie_polys is None:
            print("no SegIEPolys")
            ie_polys = SegIEPolys()
        self.ie_polys = ie_polys

        if canvas_config is None:
            canvas_config = CanvasConfig()
        self.canvas_config = canvas_config

        # UI init
        self.set_cbar_disabled()
        self.cbar.btn_poly_color_act_grp.setDisabled(False)
        self.cbar.btn_view_xseg_overlay_mask_act.setDisabled(False)
        self.cbar.btn_poly_type_act_grp.setDisabled(False)

        # Initial vars
        self.current_cursor = None
        self.mouse_hull_poly = None
        self.mouse_wire_poly = None
        self.drag_type = DragType.NONE
        self.mouse_cli_pt = np.zeros((2,), np.float32 )

        # Initial state
        self.set_op_mode(OpMode.NONE)
        self.set_color_scheme_id(1)
        self.set_poly_include_type(SegIEPolyType.INCLUDE)
        self.set_pt_edit_mode(PTEditMode.MOVE)
        self.set_view_lock(ViewLock.NONE)

        # Apply last state
        if self.last_state is not None:
            self.set_color_scheme_id(self.last_state.color_scheme_id)
            if self.last_state.op_mode is not None:
                self.set_op_mode(self.last_state.op_mode)

        self.initialized = True

        self.setMouseTracking(True)
        self.update_cursor()
        self.update()


    def finalize(self):
        if self.initialized:
            if self.op_mode == OpMode.DRAW_PTS:
                self.set_op_mode(OpMode.EDIT_PTS)

            self.last_state = sn(op_mode = self.op_mode if self.op_mode in [OpMode.VIEW_BAKED, OpMode.VIEW_XSEG_MASK] else None,
                                 color_scheme_id = self.color_scheme_id)

            self.img_pixmap = None
            self.update_cursor(is_finalize=True)
            self.setMouseTracking(False)
            self.setFocusPolicy(Qt.NoFocus)
            self.set_cbar_disabled()
            self.initialized = False
            self.update()
    # ====================================================================================
    # ====================================================================================
    # ====================================== GETTERS =====================================
    # ====================================================================================
    # ====================================================================================
    def is_initialized(self):
        return self.initialized

    def get_ie_polys(self):
        return self.ie_polys

    def get_cli_center_pt(self):
        return np.round(QSize_to_np(self.size())/2.0)

    def get_img_look_pt(self):
        img_look_pt = self.img_look_pt
        if img_look_pt is None:
            img_look_pt = self.img_size / 2
        return img_look_pt

    def get_view_scale(self):
        view_scale = self.view_scale
        if view_scale is None:
            # Calc as scale to fit
            min_cli_size = np.min(QSize_to_np(self.size()))
            max_img_size = np.max(self.img_size)
            view_scale =  min_cli_size / max_img_size

        return view_scale

    def get_current_color_scheme(self):
        return self.canvas_config.color_schemes[self.color_scheme_id]

    def get_poly_pt_id_under_pt(self, poly, cli_pt):
        w = np.argwhere ( npla.norm ( cli_pt - self.img_to_cli_pt( poly.get_pts() ), axis=1 )  <= self.canvas_config.pt_select_radius )
        return None if len(w) == 0 else w[-1][0]

    def get_poly_edge_id_pt_under_pt(self, poly, cli_pt):
        cli_pts = self.img_to_cli_pt(poly.get_pts())
        if len(cli_pts) >= 3:
            edge_dists, projs = sd.dist_to_edges(cli_pts, cli_pt, is_closed=True)
            edge_id = np.argmin(edge_dists)
            dist = edge_dists[edge_id]
            pt = projs[edge_id]
            if dist <= self.canvas_config.pt_select_radius:
                return edge_id, pt
        return None, None

    def get_poly_by_pt_near_wire(self, cli_pt):
        pt_select_radius = self.canvas_config.pt_select_radius

        for poly in reversed(self.ie_polys.get_polys()):
            pts = poly.get_pts()
            if len(pts) >= 3:
                cli_pts = self.img_to_cli_pt(pts)

                edge_dists, _ = sd.dist_to_edges(cli_pts, cli_pt, is_closed=True)

                if np.min(edge_dists) <= pt_select_radius or \
                   any( npla.norm ( cli_pt - cli_pts, axis=1 ) <= pt_select_radius ):
                    return poly
        return None

    def get_poly_by_pt_in_hull(self, cli_pos):
        img_pos = self.cli_to_img_pt(cli_pos)

        for poly in reversed(self.ie_polys.get_polys()):
            pts = poly.get_pts()
            if len(pts) >= 3:
                if cv2.pointPolygonTest( pts, tuple(img_pos), False) >= 0:
                    return poly

        return None

    def img_to_cli_pt(self, p):
        return (p - self.get_img_look_pt()) * self.get_view_scale() + self.get_cli_center_pt()# QSize_to_np(self.size())/2.0

    def cli_to_img_pt(self, p):
        return (p - self.get_cli_center_pt() ) / self.get_view_scale() + self.get_img_look_pt()

    def img_to_cli_rect(self, rect):
        tl = QPoint_to_np(rect.topLeft())
        xy = self.img_to_cli_pt(tl)
        xy2 = self.img_to_cli_pt(tl + QSize_to_np(rect.size()) ) - xy
        return QRect ( *xy.astype(np.int), *xy2.astype(np.int) )

    # ====================================================================================
    # ====================================================================================
    # ====================================== SETTERS =====================================
    # ====================================================================================
    # ====================================================================================
    def set_op_mode(self, op_mode, op_poly=None):
        if not hasattr(self,'op_mode'):
            self.op_mode = None
            self.op_poly = None

        if self.op_mode != op_mode:
            # Finalize prev mode
            if self.op_mode == OpMode.NONE:
                self.cbar.btn_poly_type_act_grp.setDisabled(True)
            elif self.op_mode == OpMode.DRAW_PTS:
                self.cbar.btn_undo_pt_act.setDisabled(True)
                self.cbar.btn_redo_pt_act.setDisabled(True)
                self.cbar.btn_view_lock_center_act.setDisabled(True)
                # Reset view_lock when exit from DRAW_PTS
                self.set_view_lock(ViewLock.NONE)
                # Remove unfinished poly
                if self.op_poly.get_pts_count() < 3:
                    self.ie_polys.remove_poly(self.op_poly)

            elif self.op_mode == OpMode.EDIT_PTS:
                self.cbar.btn_pt_edit_mode_act.setDisabled(True)
                self.cbar.btn_delete_poly_act.setDisabled(True)
                # Reset pt_edit_move when exit from EDIT_PTS
                self.set_pt_edit_mode(PTEditMode.MOVE)
            elif self.op_mode == OpMode.VIEW_BAKED:
                self.cbar.btn_view_baked_mask_act.setChecked(False)
            elif self.op_mode == OpMode.VIEW_XSEG_MASK:
                self.cbar.btn_view_xseg_mask_act.setChecked(False)

            self.op_mode = op_mode

            # Initialize new mode
            if op_mode == OpMode.NONE:
                self.cbar.btn_poly_type_act_grp.setDisabled(False)
            elif op_mode == OpMode.DRAW_PTS:
                self.cbar.btn_undo_pt_act.setDisabled(False)
                self.cbar.btn_redo_pt_act.setDisabled(False)
                self.cbar.btn_view_lock_center_act.setDisabled(False)
            elif op_mode == OpMode.EDIT_PTS:
                self.cbar.btn_pt_edit_mode_act.setDisabled(False)
                self.cbar.btn_delete_poly_act.setDisabled(False)
            elif op_mode == OpMode.VIEW_BAKED:
                self.cbar.btn_view_baked_mask_act.setChecked(True )
                n = QImage_to_np ( self.q_img ).astype(np.float32) / 255.0
                h,w,c = n.shape
                mask = np.zeros( (h,w,1), dtype=np.float32 )
                self.ie_polys.overlay_mask(mask)
                n = (mask*255).astype(np.uint8)
                self.img_baked_pixmap = QPixmap.fromImage(QImage_from_np(n))
            elif op_mode == OpMode.VIEW_XSEG_MASK:
                self.cbar.btn_view_xseg_mask_act.setChecked(True)

            if op_mode in [OpMode.DRAW_PTS, OpMode.EDIT_PTS]:
                self.mouse_op_poly_pt_id = None
                self.mouse_op_poly_edge_id = None
                self.mouse_op_poly_edge_id_pt = None

            self.op_poly = op_poly
            if op_poly is not None:
                self.update_mouse_info()

            self.update_cursor()
            self.update()

    def set_pt_edit_mode(self, pt_edit_mode):
        if not hasattr(self, 'pt_edit_mode') or self.pt_edit_mode != pt_edit_mode:
            self.pt_edit_mode = pt_edit_mode
            self.update_cursor()
            self.update()
        self.cbar.btn_pt_edit_mode_act.setChecked( self.pt_edit_mode == PTEditMode.ADD_DEL )

    def set_view_lock(self, view_lock):
        if not hasattr(self, 'view_lock') or self.view_lock != view_lock:
            if hasattr(self, 'view_lock') and self.view_lock != view_lock:
                if view_lock == ViewLock.CENTER:
                    self.img_look_pt = self.mouse_img_pt
                    QCursor.setPos ( self.mapToGlobal( QPoint_from_np(self.img_to_cli_pt(self.img_look_pt)) ))

            self.view_lock = view_lock
            self.update()
        self.cbar.btn_view_lock_center_act.setChecked( self.view_lock == ViewLock.CENTER )

    def set_cbar_disabled(self):
        self.cbar.btn_delete_poly_act.setDisabled(True)
        self.cbar.btn_undo_pt_act.setDisabled(True)
        self.cbar.btn_redo_pt_act.setDisabled(True)
        self.cbar.btn_pt_edit_mode_act.setDisabled(True)
        self.cbar.btn_view_lock_center_act.setDisabled(True)
        self.cbar.btn_poly_color_act_grp.setDisabled(True)
        self.cbar.btn_view_xseg_overlay_mask_act.setDisabled(True)
        self.cbar.btn_poly_type_act_grp.setDisabled(True)


    def set_color_scheme_id(self, id):
        if self.op_mode == OpMode.VIEW_BAKED or self.op_mode == OpMode.VIEW_XSEG_MASK:
            self.set_op_mode(OpMode.NONE)

        if not hasattr(self, 'color_scheme_id') or self.color_scheme_id != id:
            self.color_scheme_id = id
            self.update_cursor()
            self.update()

        if self.color_scheme_id == 0:
            self.cbar.btn_poly_color_red_act.setChecked( True )
        elif self.color_scheme_id == 1:
            self.cbar.btn_poly_color_green_act.setChecked( True )
        elif self.color_scheme_id == 2:
            self.cbar.btn_poly_color_blue_act.setChecked( True )

    def set_poly_include_type(self, poly_include_type):
        if not hasattr(self, 'poly_include_type' ) or \
           ( self.poly_include_type != poly_include_type and \
             self.op_mode in [OpMode.NONE, OpMode.EDIT_PTS] ):
            self.poly_include_type = poly_include_type
            self.update()
        self.cbar.btn_poly_type_include_act.setChecked(self.poly_include_type == SegIEPolyType.INCLUDE)
        self.cbar.btn_poly_type_exclude_act.setChecked(self.poly_include_type == SegIEPolyType.EXCLUDE)

    # ====================================================================================
    # ====================================================================================
    # ====================================== METHODS =====================================
    # ====================================================================================
    # ====================================================================================

    def update_cursor(self, is_finalize=False):
        if not self.initialized:
            return

        if not self.mouse_in_widget or is_finalize:
            if self.current_cursor is not None:
                QApplication.restoreOverrideCursor()
                self.current_cursor = None
        else:
            color_cc = self.get_current_color_scheme().cross_cursor
            nc = Qt.ArrowCursor

            if self.drag_type == DragType.IMAGE_LOOK:
                nc = Qt.ClosedHandCursor
            else:

                if self.op_mode == OpMode.NONE:
                    nc = color_cc
                    if self.mouse_wire_poly is not None:
                        nc = Qt.PointingHandCursor

                elif self.op_mode == OpMode.DRAW_PTS:
                    nc = color_cc
                elif self.op_mode == OpMode.EDIT_PTS:
                    nc = Qt.ArrowCursor

                    if self.mouse_op_poly_pt_id is not None:
                        nc = Qt.PointingHandCursor

                    if self.pt_edit_mode == PTEditMode.ADD_DEL:

                        if self.mouse_op_poly_edge_id is not None and \
                        self.mouse_op_poly_pt_id is None:
                            nc = color_cc
            if self.current_cursor != nc:
                if self.current_cursor is None:
                    QApplication.setOverrideCursor(nc)
                else:
                    QApplication.changeOverrideCursor(nc)
                self.current_cursor = nc

    def update_mouse_info(self, mouse_cli_pt=None):
        """
        Update selected polys/edges/points by given mouse position
        """
        if mouse_cli_pt is not None:
            self.mouse_cli_pt = mouse_cli_pt.astype(np.float32)

        self.mouse_img_pt = self.cli_to_img_pt(self.mouse_cli_pt)

        new_mouse_hull_poly = self.get_poly_by_pt_in_hull(self.mouse_cli_pt)

        if self.mouse_hull_poly != new_mouse_hull_poly:
            self.mouse_hull_poly = new_mouse_hull_poly
            self.update_cursor()
            self.update()

        new_mouse_wire_poly = self.get_poly_by_pt_near_wire(self.mouse_cli_pt)

        if self.mouse_wire_poly != new_mouse_wire_poly:
            self.mouse_wire_poly = new_mouse_wire_poly
            self.update_cursor()
            self.update()

        if self.op_mode in [OpMode.DRAW_PTS, OpMode.EDIT_PTS]:
            new_mouse_op_poly_pt_id = self.get_poly_pt_id_under_pt (self.op_poly, self.mouse_cli_pt)
            if self.mouse_op_poly_pt_id != new_mouse_op_poly_pt_id:
                self.mouse_op_poly_pt_id = new_mouse_op_poly_pt_id
                self.update_cursor()
                self.update()

            new_mouse_op_poly_edge_id,\
            new_mouse_op_poly_edge_id_pt = self.get_poly_edge_id_pt_under_pt (self.op_poly, self.mouse_cli_pt)
            if self.mouse_op_poly_edge_id != new_mouse_op_poly_edge_id:
                self.mouse_op_poly_edge_id = new_mouse_op_poly_edge_id
                self.update_cursor()
                self.update()

            if (self.mouse_op_poly_edge_id_pt.__class__ != new_mouse_op_poly_edge_id_pt.__class__) or \
               (isinstance(self.mouse_op_poly_edge_id_pt, np.ndarray) and \
                all(self.mouse_op_poly_edge_id_pt != new_mouse_op_poly_edge_id_pt)):

                self.mouse_op_poly_edge_id_pt = new_mouse_op_poly_edge_id_pt
                self.update_cursor()
                self.update()


    def action_undo_pt(self):
        if self.drag_type == DragType.NONE:
            if self.op_mode == OpMode.DRAW_PTS:
                if self.op_poly.undo() == 0:
                    self.ie_polys.remove_poly (self.op_poly)
                    self.set_op_mode(OpMode.NONE)
                self.update()

    def action_redo_pt(self):
        if self.drag_type == DragType.NONE:
            if self.op_mode == OpMode.DRAW_PTS:
                self.op_poly.redo()
                self.update()

    def action_delete_poly(self):
        if self.op_mode == OpMode.EDIT_PTS and \
            self.drag_type == DragType.NONE and \
            self.pt_edit_mode == PTEditMode.MOVE:
            # Delete current poly
            self.ie_polys.remove_poly (self.op_poly)
            self.set_op_mode(OpMode.NONE)
    # ====================================================================================
    # ====================================================================================
    # ================================== OVERRIDE QT METHODS =============================
    # ====================================================================================
    # ====================================================================================
    def on_keyPressEvent(self, ev):
        if not self.initialized:
            return
        key = ev.key()
        key_mods = int(ev.modifiers())
        if self.op_mode == OpMode.DRAW_PTS:
            self.set_view_lock(ViewLock.CENTER if key_mods == Qt.ShiftModifier else ViewLock.NONE )
        elif self.op_mode == OpMode.EDIT_PTS:
            self.set_pt_edit_mode(PTEditMode.ADD_DEL if key_mods == Qt.ControlModifier else PTEditMode.MOVE )

    def on_keyReleaseEvent(self, ev):
        if not self.initialized:
            return
        key = ev.key()
        key_mods = int(ev.modifiers())
        if self.op_mode == OpMode.DRAW_PTS:
            self.set_view_lock(ViewLock.CENTER if key_mods == Qt.ShiftModifier else ViewLock.NONE )
        elif self.op_mode == OpMode.EDIT_PTS:
            self.set_pt_edit_mode(PTEditMode.ADD_DEL if key_mods == Qt.ControlModifier else PTEditMode.MOVE )

    def enterEvent(self, ev):
        super().enterEvent(ev)
        self.mouse_in_widget = True
        self.update_cursor()

    def leaveEvent(self, ev):
        super().leaveEvent(ev)
        self.mouse_in_widget = False
        self.update_cursor()

    def mousePressEvent(self, ev):
        super().mousePressEvent(ev)
        if not self.initialized:
            return

        self.update_mouse_info(QPoint_to_np(ev.pos()))

        btn = ev.button()

        if btn == Qt.LeftButton:
            if self.op_mode == OpMode.NONE:
                # Clicking in NO OPERATION mode
                if self.mouse_wire_poly is not None:
                    # Click on wire on any poly -> switch to EDIT_MODE
                    self.set_op_mode(OpMode.EDIT_PTS, op_poly=self.mouse_wire_poly)
                else:
                    # Click on empty space -> create new poly with one point
                    new_poly = self.ie_polys.add_poly(self.poly_include_type)
                    self.ie_polys.sort()
                    new_poly.add_pt(*self.mouse_img_pt)
                    self.set_op_mode(OpMode.DRAW_PTS, op_poly=new_poly )

            elif self.op_mode == OpMode.DRAW_PTS:
                # Clicking in DRAW_PTS mode
                if len(self.op_poly.get_pts()) >= 3 and self.mouse_op_poly_pt_id == 0:
                    # Click on first point -> close poly and switch to edit mode
                    self.set_op_mode(OpMode.EDIT_PTS, op_poly=self.op_poly)
                else:
                    # Click on empty space -> add point to current poly
                    self.op_poly.add_pt(*self.mouse_img_pt)
                    self.update()

            elif self.op_mode == OpMode.EDIT_PTS:
                # Clicking in EDIT_PTS mode

                if self.mouse_op_poly_pt_id is not None:
                    # Click on point of op_poly
                    if self.pt_edit_mode == PTEditMode.ADD_DEL:
                        # in mode 'delete point'
                        self.op_poly.remove_pt(self.mouse_op_poly_pt_id)
                        if self.op_poly.get_pts_count() < 3:
                            # not enough points after delete -> remove poly
                            self.ie_polys.remove_poly (self.op_poly)
                            self.set_op_mode(OpMode.NONE)
                        self.update()

                    elif self.drag_type == DragType.NONE:
                        # otherwise -> start drag
                        self.drag_type = DragType.POLY_PT
                        self.drag_cli_pt     = self.mouse_cli_pt
                        self.drag_poly_pt_id = self.mouse_op_poly_pt_id
                        self.drag_poly_pt    = self.op_poly.get_pts()[ self.drag_poly_pt_id ]
                elif self.mouse_op_poly_edge_id is not None:
                    # Click on edge of op_poly
                    if self.pt_edit_mode == PTEditMode.ADD_DEL:
                        # in mode 'insert new point'
                        edge_img_pt = self.cli_to_img_pt(self.mouse_op_poly_edge_id_pt)
                        self.op_poly.insert_pt (self.mouse_op_poly_edge_id+1, edge_img_pt)
                        self.update()
                    else:
                        # Otherwise do nothing
                        pass
                else:
                    # other cases -> unselect poly
                    self.set_op_mode(OpMode.NONE)

        elif btn == Qt.MiddleButton:
            if self.drag_type == DragType.NONE:
                # Start image drag
                self.drag_type = DragType.IMAGE_LOOK
                self.drag_cli_pt = self.mouse_cli_pt
                self.drag_img_look_pt = self.get_img_look_pt()
                self.update_cursor()


    def mouseReleaseEvent(self, ev):
        super().mouseReleaseEvent(ev)
        if not self.initialized:
            return

        self.update_mouse_info(QPoint_to_np(ev.pos()))

        btn = ev.button()

        if btn == Qt.LeftButton:
            if self.op_mode == OpMode.EDIT_PTS:
                if self.drag_type == DragType.POLY_PT:
                    self.drag_type = DragType.NONE
                    self.update()

        elif btn == Qt.MiddleButton:
            if self.drag_type == DragType.IMAGE_LOOK:
                self.drag_type = DragType.NONE
                self.update_cursor()
                self.update()

    def mouseMoveEvent(self, ev):
        super().mouseMoveEvent(ev)
        if not self.initialized:
            return

        prev_mouse_cli_pt = self.mouse_cli_pt
        self.update_mouse_info(QPoint_to_np(ev.pos()))

        if self.view_lock == ViewLock.CENTER:
            if npla.norm(self.mouse_cli_pt - prev_mouse_cli_pt) >= 1:
                self.img_look_pt = self.mouse_img_pt
                QCursor.setPos ( self.mapToGlobal( QPoint_from_np(self.img_to_cli_pt(self.img_look_pt)) ))
            self.update()

        if self.drag_type == DragType.IMAGE_LOOK:
            delta_pt = self.cli_to_img_pt(self.mouse_cli_pt) - self.cli_to_img_pt(self.drag_cli_pt)
            self.img_look_pt = self.drag_img_look_pt - delta_pt
            self.update()

        if self.op_mode == OpMode.DRAW_PTS:
            self.update()
        elif self.op_mode == OpMode.EDIT_PTS:
            if self.drag_type == DragType.POLY_PT:
                delta_pt = self.cli_to_img_pt(self.mouse_cli_pt) - self.cli_to_img_pt(self.drag_cli_pt)
                self.op_poly.set_point(self.drag_poly_pt_id, self.drag_poly_pt + delta_pt)
                self.update()

    def wheelEvent(self, ev):
        super().wheelEvent(ev)

        if not self.initialized:
            return

        mods = int(ev.modifiers())
        delta = ev.angleDelta()

        cli_pt = QPoint_to_np(ev.pos())

        if self.drag_type == DragType.NONE:
            sign = np.sign( delta.y() )
            prev_img_pos = self.cli_to_img_pt (cli_pt)
            delta_scale = sign*0.2 + sign * self.get_view_scale() / 10.0
            self.view_scale = np.clip(self.get_view_scale() + delta_scale, 1.0, 20.0)
            new_img_pos = self.cli_to_img_pt (cli_pt)
            if sign > 0:
                self.img_look_pt = self.get_img_look_pt() + (prev_img_pos-new_img_pos)#*1.5
            else:
                QCursor.setPos ( self.mapToGlobal(QPoint_from_np(self.img_to_cli_pt(prev_img_pos))) )
            self.update()

    def paintEvent(self, event):
        super().paintEvent(event)
        if not self.initialized:
            return

        qp = self.qp
        qp.begin(self)
        qp.setRenderHint(QPainter.Antialiasing)
        qp.setRenderHint(QPainter.HighQualityAntialiasing)
        qp.setRenderHint(QPainter.SmoothPixmapTransform)

        src_rect = QRect(0, 0, *self.img_size)
        dst_rect = self.img_to_cli_rect( src_rect )

        if self.op_mode == OpMode.VIEW_BAKED:
            qp.drawPixmap(dst_rect, self.img_baked_pixmap, src_rect)
        elif self.op_mode == OpMode.VIEW_XSEG_MASK:
            if self.xseg_mask_pixmap is not None:
                qp.drawPixmap(dst_rect, self.xseg_mask_pixmap, src_rect)
        else:
            if self.cbar.btn_view_xseg_overlay_mask_act.isChecked() and \
               self.xseg_overlay_mask_pixmap is not None:
                qp.drawPixmap(dst_rect, self.xseg_overlay_mask_pixmap, src_rect)
            elif self.img_pixmap is not None:
                qp.drawPixmap(dst_rect, self.img_pixmap, src_rect)

            polys = self.ie_polys.get_polys()
            polys_len = len(polys)

            color_scheme = self.get_current_color_scheme()

            pt_rad = self.canvas_config.pt_radius
            pt_rad_x2 = pt_rad*2

            pt_select_radius = self.canvas_config.pt_select_radius

            op_mode = self.op_mode
            op_poly = self.op_poly

            for i,poly in enumerate(polys):

                selected_pt_path = QPainterPath()
                poly_line_path = QPainterPath()
                pts_line_path = QPainterPath()

                pt_remove_cli_pt = None
                poly_pts = poly.get_pts()
                for pt_id, img_pt in enumerate(poly_pts):
                    cli_pt = self.img_to_cli_pt(img_pt)
                    q_cli_pt = QPoint_from_np(cli_pt)

                    if pt_id == 0:
                        poly_line_path.moveTo(q_cli_pt)
                    else:
                        poly_line_path.lineTo(q_cli_pt)


                    if poly == op_poly:
                        if self.op_mode == OpMode.DRAW_PTS or \
                            (self.op_mode == OpMode.EDIT_PTS and \
                            (self.pt_edit_mode == PTEditMode.MOVE) or \
                            (self.pt_edit_mode == PTEditMode.ADD_DEL and self.mouse_op_poly_pt_id == pt_id) \
                            ):
                            pts_line_path.moveTo( QPoint_from_np(cli_pt + np.float32([0,-pt_rad])) )
                            pts_line_path.lineTo( QPoint_from_np(cli_pt + np.float32([0,pt_rad])) )
                            pts_line_path.moveTo( QPoint_from_np(cli_pt + np.float32([-pt_rad,0])) )
                            pts_line_path.lineTo( QPoint_from_np(cli_pt + np.float32([pt_rad,0])) )

                        if (self.op_mode == OpMode.EDIT_PTS and \
                            self.pt_edit_mode == PTEditMode.ADD_DEL and \
                            self.mouse_op_poly_pt_id == pt_id):
                            pt_remove_cli_pt = cli_pt

                        if self.op_mode == OpMode.DRAW_PTS and \
                            len(op_poly.get_pts()) >= 3 and pt_id == 0 and self.mouse_op_poly_pt_id == pt_id:
                            # Circle around poly point
                            selected_pt_path.addEllipse(q_cli_pt, pt_rad_x2, pt_rad_x2)


                if poly == op_poly:
                    if op_mode == OpMode.DRAW_PTS:
                        # Line from last point to mouse
                        poly_line_path.lineTo( QPoint_from_np(self.mouse_cli_pt) )

                    if self.mouse_op_poly_pt_id is not None:
                        pass

                    if self.mouse_op_poly_edge_id_pt is not None:
                        if self.pt_edit_mode == PTEditMode.ADD_DEL and self.mouse_op_poly_pt_id is None:
                            # Ready to insert point on edge
                            m_cli_pt = self.mouse_op_poly_edge_id_pt
                            pts_line_path.moveTo( QPoint_from_np(m_cli_pt + np.float32([0,-pt_rad])) )
                            pts_line_path.lineTo( QPoint_from_np(m_cli_pt + np.float32([0,pt_rad])) )
                            pts_line_path.moveTo( QPoint_from_np(m_cli_pt + np.float32([-pt_rad,0])) )
                            pts_line_path.lineTo( QPoint_from_np(m_cli_pt + np.float32([pt_rad,0])) )

                if len(poly_pts) >= 2:
                    # Closing poly line
                    poly_line_path.lineTo( QPoint_from_np(self.img_to_cli_pt(poly_pts[0])) )

                # Draw calls
                qp.setPen(color_scheme.pt_outline_pen)
                qp.setBrush(QBrush())
                qp.drawPath(selected_pt_path)

                qp.setPen(color_scheme.poly_outline_solid_pen)
                qp.setBrush(QBrush())
                qp.drawPath(pts_line_path)

                if poly.get_type() == SegIEPolyType.INCLUDE:
                    qp.setPen(color_scheme.poly_outline_solid_pen)
                else:
                    qp.setPen(color_scheme.poly_outline_dot_pen)

                qp.setBrush(color_scheme.poly_unselected_brush)
                if op_mode == OpMode.NONE:
                    if poly == self.mouse_wire_poly:
                        qp.setBrush(color_scheme.poly_selected_brush)
                #else:
                #    if poly == op_poly:
                #        qp.setBrush(color_scheme.poly_selected_brush)
		#edit Lowie

                qp.drawPath(poly_line_path)

                if pt_remove_cli_pt is not None:
                    qp.setPen(color_scheme.poly_outline_solid_pen)
                    qp.setBrush(QBrush())

                    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])) )
                    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])) )

        qp.end()

class QCanvas(QFrame):
    def __init__(self):
        super().__init__()

        self.canvas_control_left_bar = QCanvasControlsLeftBar()
        self.canvas_control_right_bar = QCanvasControlsRightBar()

        cbar = sn( btn_poly_color_red_act   = self.canvas_control_right_bar.btn_poly_color_red_act,
                   btn_poly_color_green_act = self.canvas_control_right_bar.btn_poly_color_green_act,
                   btn_poly_color_blue_act  = self.canvas_control_right_bar.btn_poly_color_blue_act,
                   btn_view_baked_mask_act  = self.canvas_control_right_bar.btn_view_baked_mask_act,
                   btn_view_xseg_mask_act  = self.canvas_control_right_bar.btn_view_xseg_mask_act,
                   btn_view_xseg_overlay_mask_act  = self.canvas_control_right_bar.btn_view_xseg_overlay_mask_act,
                   btn_poly_color_act_grp = self.canvas_control_right_bar.btn_poly_color_act_grp,
                   btn_view_lock_center_act = self.canvas_control_right_bar.btn_view_lock_center_act,

                   btn_poly_type_include_act = self.canvas_control_left_bar.btn_poly_type_include_act,
                   btn_poly_type_exclude_act = self.canvas_control_left_bar.btn_poly_type_exclude_act,
                   btn_poly_type_act_grp = self.canvas_control_left_bar.btn_poly_type_act_grp,
                   btn_undo_pt_act = self.canvas_control_left_bar.btn_undo_pt_act,
                   btn_redo_pt_act = self.canvas_control_left_bar.btn_redo_pt_act,
                   btn_delete_poly_act = self.canvas_control_left_bar.btn_delete_poly_act,
                   btn_pt_edit_mode_act = self.canvas_control_left_bar.btn_pt_edit_mode_act )

        self.operate_canvas = QCanvasOperator(cbar)
        self.ui_layout = QHBoxLayout()
        self.ui_layout.setContentsMargins(0,0,0,0)
        self.ui_layout.addWidget(self.canvas_control_left_bar)
        self.ui_layout.addWidget(self.operate_canvas)
        self.ui_layout.addWidget(self.canvas_control_right_bar)
        self.setLayout(self.ui_layout)

class LoaderQSubprocessor(QSubprocessor):
    def __init__(self, image_paths, q_label, q_progressbar, on_finish_func ):

        self.image_paths = image_paths
        self.image_paths_len = len(image_paths)
        self.idxs = [*range(self.image_paths_len)]

        self.filtered_image_paths = self.image_paths.copy()

        self.image_paths_has_ie_polys = { image_path : False for image_path in self.image_paths }

        self.q_label = q_label
        self.q_progressbar = q_progressbar
        self.q_progressbar.setRange(0, self.image_paths_len)
        self.q_progressbar.setValue(0)
        self.q_progressbar.update()
        self.on_finish_func = on_finish_func
        self.done_count = 0
        super().__init__('LoaderQSubprocessor', LoaderQSubprocessor.Cli, 60)

    def get_data(self, host_dict):
        if len (self.idxs) > 0:
            idx = self.idxs.pop(0)
            image_path = self.image_paths[idx]
            self.q_label.setText(f'{QStringDB.loading_tip}... {image_path.name}')

            return idx, image_path

        return None

    def on_clients_finalized(self):
        self.on_finish_func([x for x in self.filtered_image_paths if x is not None], self.image_paths_has_ie_polys)

    def on_data_return (self, host_dict, data):
        self.idxs.insert(0, data[0])

    def on_result (self, host_dict, data, result):
        idx, has_dflimg, has_ie_polys = result

        if not has_dflimg:
            self.filtered_image_paths[idx] = None
        self.image_paths_has_ie_polys[self.image_paths[idx]] = has_ie_polys

        self.done_count += 1
        if self.q_progressbar is not None:
            self.q_progressbar.setValue(self.done_count)

    class Cli(QSubprocessor.Cli):
        def process_data(self, data):
            idx, filename = data
            dflimg = DFLIMG.load(filename)
            if dflimg is not None and dflimg.has_data():
                ie_polys = dflimg.get_seg_ie_polys()

                return idx, True, ie_polys.has_polys()
            return idx, False, False

class MainWindow(QXMainWindow):

    def __init__(self, input_dirpath, cfg_root_path):
        self.loading_frame = None
        self.help_frame = None

        super().__init__()

        self.input_dirpath = input_dirpath
        self.trash_dirpath = input_dirpath.parent / (input_dirpath.name + '_trash')
        self.cfg_root_path = cfg_root_path

        self.cfg_path = cfg_root_path / 'MainWindow_cfg.dat'
        self.cfg_dict = pickle.loads(self.cfg_path.read_bytes()) if self.cfg_path.exists() else {}

        self.cached_images = {}
        self.cached_has_ie_polys = {}

        self.initialize_ui()

        # Loader (Loading folder image progress bar)
        self.loading_frame = QFrame(self.main_canvas_frame)
        self.loading_frame.setAutoFillBackground(True)
        self.loading_frame.setFrameShape(QFrame.StyledPanel)
        self.loader_label = QLabel()
        self.loader_progress_bar = QProgressBar()

        intro_image = QLabel()
        intro_image.setPixmap( QPixmap.fromImage(QImageDB.intro) )

        intro_image_frame_l = QVBoxLayout()
        intro_image_frame_l.addWidget(intro_image, alignment=Qt.AlignCenter)
        intro_image_frame = QFrame()
        intro_image_frame.setSizePolicy (QSizePolicy.Expanding, QSizePolicy.Expanding)
        intro_image_frame.setLayout(intro_image_frame_l)

        loading_frame_l = QVBoxLayout()
        loading_frame_l.addWidget (intro_image_frame)
        loading_frame_l.addWidget (self.loader_label)
        loading_frame_l.addWidget (self.loader_progress_bar)
        self.loading_frame.setLayout(loading_frame_l)

        self.loader_subprocessor = LoaderQSubprocessor( image_paths=pathex.get_image_paths(input_dirpath, return_Path_class=True),
                                                        q_label=self.loader_label,
                                                        q_progressbar=self.loader_progress_bar,
                                                        on_finish_func=self.on_loader_finish )


    def on_loader_finish(self, image_paths, image_paths_has_ie_polys):
        self.image_paths_done = []
        self.image_paths = image_paths
        self.image_paths_has_ie_polys = image_paths_has_ie_polys
        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]) )
        self.loading_frame.hide()
        self.loading_frame = None

        self.onclick_next_image(first_initialization=True)

    def closeEvent(self, ev):
        self.cfg_dict['geometry'] = self.saveGeometry().data()
        self.cfg_path.write_bytes( pickle.dumps(self.cfg_dict) )


    def update_cached_images (self, count=5):
        d = self.cached_images

        for image_path in self.image_paths_done[:-count]+self.image_paths[count:]:
            if image_path in d:
                del d[image_path]

        for image_path in self.image_paths[:count]+self.image_paths_done[-count:]:
            if image_path not in d:
                img = cv2_imread(image_path)
                if img is not None:
                    d[image_path] = img

    def load_image(self, image_path):
        try:
            img = self.cached_images.get(image_path, None)
            if img is None:
                img = cv2_imread(image_path)
                self.cached_images[image_path] = img
            if img is None:
                io.log_err(f'Unable to load {image_path}')
        except:
            img = None

        return img

    def update_preview_bar(self):
        count = self.image_bar.get_preview_images_count()
        d = self.cached_images
        prev_imgs = [ d.get(image_path, None) for image_path in self.image_paths_done[-1:-count:-1] ]
        next_imgs = [ d.get(image_path, None) for image_path in self.image_paths[:count] ]
        self.image_bar.update_images(prev_imgs, next_imgs)


    def canvas_initialize(self, image_path, only_has_polys=False):
        if only_has_polys and not self.image_paths_has_ie_polys[image_path]:
            return False
        dflimg = DFLIMG.load(image_path)
        if not dflimg or not dflimg.has_data():
            return False
        ie_polys = dflimg.get_seg_ie_polys()
        xseg_mask = dflimg.get_xseg_mask()
        img = self.load_image(image_path)
        if img is None:
            return False
        self.canvas.operate_canvas.initialize ( img,  ie_polys=ie_polys, xseg_mask=xseg_mask )
        #self.folder_label.setText(f"{self.input_dirpath}")
        self.filename_label.setText(f"{self.input_dirpath}\\{image_path.name}")
        return True

    def canvas_finalize(self, image_path,convert_mask=False):
        self.canvas.operate_canvas.finalize()

        if image_path.exists():
            dflimg = DFLIMG.load(image_path)
            ie_polys = dflimg.get_seg_ie_polys()
            new_ie_polys = self.canvas.operate_canvas.get_ie_polys()

            if not new_ie_polys.identical(ie_polys):
                prev_has_polys = self.image_paths_has_ie_polys[image_path]
                self.image_paths_has_ie_polys[image_path] = new_ie_polys.has_polys()
                new_has_polys = self.image_paths_has_ie_polys[image_path]

                if not prev_has_polys and new_has_polys:
                    self.set_has_ie_polys_count ( self.get_has_ie_polys_count() +1)
                elif prev_has_polys and not new_has_polys:
                    self.set_has_ie_polys_count ( self.get_has_ie_polys_count() -1)

                dflimg.set_seg_ie_polys( new_ie_polys )

            if self.autosave_checkbox.isChecked() or convert_mask:
                if new_ie_polys.has_polys():
                    self.apply_canvas_polygon_to_mask(dflimg,new_ie_polys)
            dflimg.save()

        self.filename_label.setText(f"")

    def apply_canvas_polygon_to_mask(self,dflimg,seg_ie_polys,reso=256):

        mask=np.zeros_like(dflimg.get_img()).astype(float)
        if seg_ie_polys is not None:
            if seg_ie_polys.has_polys():
                seg_ie_polys.overlay_mask(mask)
        mask=cv2.resize(mask,(reso,reso))
        dflimg.set_xseg_mask(mask)

   

    def onclick_prev_image(self):
        key_mods = QApplication.keyboardModifiers()
        step =100 if key_mods == Qt.ShiftModifier else 1
        only_has_polys = key_mods == Qt.ControlModifier

        if self.canvas.operate_canvas.is_initialized():
            self.canvas_finalize(self.image_paths[0])

        while True:
            for _ in range(step):
                if len(self.image_paths_done) != 0:
                    self.image_paths.insert (0, self.image_paths_done.pop(-1))
                else:
                    break
            if len(self.image_paths) == 0:
                break

            ret = self.canvas_initialize(self.image_paths[0], len(self.image_paths_done) != 0 and only_has_polys)

            if ret or len(self.image_paths_done) == 0:
                break

        self.update_cached_images()
        self.update_preview_bar()

    def onclick_next_image(self, first_initialization=False):
        key_mods = QApplication.keyboardModifiers()

        step = 0 if first_initialization else 100 if key_mods == Qt.ShiftModifier else 1
        only_has_polys = False if first_initialization else key_mods == Qt.ControlModifier

        if self.canvas.operate_canvas.is_initialized():
            self.canvas_finalize(self.image_paths[0])

        while True:
            for _ in range(step):
                if len(self.image_paths) != 0:
                    self.image_paths_done.append(self.image_paths.pop(0))
                else:
                    break
            if len(self.image_paths) == 0:
                break
            if self.canvas_initialize(self.image_paths[0], only_has_polys):
                break

        self.update_cached_images()
        self.update_preview_bar()
        
    def trash_current_image(self):
        self.onclick_next_image()
        
        img_path = self.image_paths_done.pop(-1)
        img_path = Path(img_path)
        self.trash_dirpath.mkdir(parents=True, exist_ok=True)
        img_path.rename( self.trash_dirpath / img_path.name )
        
        self.update_cached_images()
        self.update_preview_bar()
        
    def onclick_from_landmark_points(self):
        self.showNotify("Extract editing polygons from feature points")
        image_path=self.image_paths[0]
        #print(image_path)
        if image_path.exists():
            dflimg = DFLIMG.load(image_path)
            dflimg.create_seg_polygon_from_landmarks()
        self.canvas_initialize(self.image_paths[0])

    def onclick_from_xseg_mask(self):        
        image_path=self.image_paths[0]
        if image_path.exists():
            dflimg = DFLIMG.load(image_path)
            dflimg.create_seg_polygon_from_mask()
        self.canvas_initialize(self.image_paths[0])
        self.showNotify("Already extracted editing polygons from XSeg")

    def onclick_clear_polygons(self):        
        image_path=self.image_paths[0]
        if image_path.exists():
            dflimg = DFLIMG.load(image_path)
            dflimg.clear_ie_polygon()
        self.canvas_initialize(self.image_paths[0])
        self.showNotify("Clear Edit Polygon")

    def onclick_mouth_mask(self):
        image_path=self.image_paths[0]
        if image_path.exists():
            dflimg = DFLIMG.load(image_path)
            dflimg.create_mouth_ie_polygon()
        self.canvas_initialize(self.image_paths[0])
        self.showNotify("Mouth area editable polygon has been added")

    def onclick_teeth_mask(self):        
        image_path=self.image_paths[0]
        if image_path.exists():
            dflimg = DFLIMG.load(image_path)
            dflimg.create_teeth_ie_polygon()
        self.canvas_initialize(self.image_paths[0])
        self.showNotify("Editable polygons have been added for intraoral tooth areas")
    def onclick_eyes_mask(self):
        self.showNotify("Extracting the eye area")
        image_path=self.image_paths[0]
        if image_path.exists():
            dflimg = DFLIMG.load(image_path)
            dflimg.create_eyes_ie_polygon()
        self.canvas_initialize(self.image_paths[0])

    def onclick_convert_xseg_mask(self):
        image_path=self.image_paths[0]
        if image_path.exists():
            dflimg = DFLIMG.load(image_path)
            self.apply_canvas_polygon_to_mask(dflimg, self.canvas.operate_canvas.get_ie_polys())
            self.canvas_finalize(image_path,convert_mask=True)
            self.showNotify("Has been converted to an Xseg mask")

    def onclick_save_edit(self):
        image_path=self.image_paths[0]
        if image_path.exists():
            if self.canvas.operate_canvas.is_initialized():
                self.canvas_finalize(self.image_paths[0],convert_mask=True)
            self.canvas_initialize(self.image_paths[0], False)
            self.showNotify("Save changes")

    def showNotify(self,str):
        timestr=time.strftime("%H:%M:%S",time.localtime())
        self.tips_label.setText(f"[{timestr}]{str}")
    #-----Setting interface
    def initialize_ui(self):

        self.canvas = QCanvas()

        image_bar = self.image_bar = ImagePreviewSequenceBar(preview_images_count=9, icon_size=QUIConfig.preview_bar_icon_q_size.width())
        image_bar.setSizePolicy ( QSizePolicy.Fixed, QSizePolicy.Fixed )

        btn_from_landmark_points = QXIconButton(QIconDB.landmark,"create editable polygons from landmarks points \n", shortcut='L', click_func=self.onclick_from_landmark_points)
        btn_from_landmark_points.setIconSize(QUIConfig.icon_q_size)

        btn_from_xseg_mask = QXIconButton(QIconDB.xseg_mask, "create editable polygons from xseg mask \n", shortcut='M', click_func=self.onclick_from_xseg_mask)
        btn_from_xseg_mask.setIconSize(QUIConfig.icon_q_size)

        btn_clear_polygons = QXIconButton(QIconDB.clear, "clear all editable polygons\n", shortcut='C', click_func=self.onclick_clear_polygons)
        btn_clear_polygons.setIconSize(QUIConfig.icon_q_size)

        btn_mouth_mask = QXIconButton(QIconDB.mouth, "extract mouth area polygon\n", shortcut='P', click_func=self.onclick_mouth_mask)
        btn_mouth_mask.setIconSize(QUIConfig.icon_q_size)

        btn_eyes_area = QXIconButton(QIconDB.eyes, "extract eyes area polygon\n", shortcut='E', click_func=self.onclick_eyes_mask)
        btn_eyes_area.setIconSize(QUIConfig.icon_q_size)

        btn_teeth_mask = QXIconButton(QIconDB.teeth, "get teeth(inner mouth) area polygon \n", shortcut='T', click_func=self.onclick_teeth_mask)
        btn_teeth_mask.setIconSize(QUIConfig.icon_q_size)

        btn_calc_mask_image = QXIconButton(QIconDB.ok, "calculate mask from editable polygon", shortcut='V', click_func=self.onclick_convert_xseg_mask)
        btn_calc_mask_image.setIconSize(QUIConfig.icon_q_size)  

        btn_save_image = QXIconButton(QIconDB.save, "save result", shortcut='S', click_func=self.onclick_save_edit)
        btn_save_image.setIconSize(QUIConfig.icon_q_size)     

      

        #--- Execute image manipulation button block
        image_operate_bar_layout = QHBoxLayout()
        image_operate_bar_layout.setContentsMargins(0,0,0,0)
           
        image_operate_bar_layout.addWidget ( btn_eyes_area, alignment=Qt.AlignCenter)
        image_operate_bar_layout.addWidget ( btn_mouth_mask, alignment=Qt.AlignCenter)
        image_operate_bar_layout.addWidget ( btn_teeth_mask, alignment=Qt.AlignCenter) 
        image_operate_bar_layout.addWidget ( btn_from_landmark_points, alignment=Qt.AlignCenter)
        image_operate_bar_layout.addWidget ( btn_from_xseg_mask, alignment=Qt.AlignCenter)     
        image_operate_bar_layout.addWidget ( btn_teeth_mask, alignment=Qt.AlignCenter)
        image_operate_bar_layout.addWidget ( btn_teeth_mask, alignment=Qt.AlignCenter)

        image_operate_bar_layout.addWidget ( btn_clear_polygons, alignment=Qt.AlignCenter)
        #image_operate_bar_layout.addWidget ( btn_calc_mask_image, alignment=Qt.AlignCenter)
        image_operate_bar_layout.addWidget ( btn_save_image, alignment=Qt.AlignCenter)    

        image_operate_bar_frame = QFrame()
        image_operate_bar_frame.setSizePolicy ( QSizePolicy.Fixed, QSizePolicy.Fixed )
        image_operate_bar_frame.setLayout(image_operate_bar_layout)
        
        #--Navigation blocks before and after image preview
        preview_image_bar_layout_l = QHBoxLayout()
        preview_image_bar_layout_l.setContentsMargins(0,0,0,0)

        btn_delete_image = QXIconButton(QIconDB.trashcan, QStringDB.btn_delete_image_tip, shortcut='X', click_func=self.trash_current_image)
        btn_delete_image.setIconSize(QUIConfig.icon_q_size)

        btn_prev_image = QXIconButton(QIconDB.left, QStringDB.btn_prev_image_tip, shortcut='A', click_func=self.onclick_prev_image)
        btn_prev_image.setIconSize(QUIConfig.preview_bar_icon_q_size)

        btn_next_image = QXIconButton(QIconDB.right, QStringDB.btn_next_image_tip, shortcut='D', click_func=self.onclick_next_image)
        btn_next_image.setIconSize(QUIConfig.preview_bar_icon_q_size)

        preview_image_bar_layout_l.addWidget ( btn_prev_image, alignment=Qt.AlignCenter)
        preview_image_bar_layout_l.addWidget ( image_bar)
        preview_image_bar_layout_l.addWidget ( btn_next_image, alignment=Qt.AlignCenter)
        preview_image_bar_layout_l.addWidget ( btn_delete_image, alignment=Qt.AlignCenter)
        preview_image_bar = QFrame()
        preview_image_bar.setFrameShape(QFrame.StyledPanel)
        preview_image_bar.setSizePolicy ( QSizePolicy.Expanding, QSizePolicy.Fixed )
        preview_image_bar.setLayout(preview_image_bar_layout_l)

        #-- Defining Status Label Controls
        label_font = QFont('Courier New')
        self.filename_label = QLabel()
        self.filename_label.setFont(label_font)
        self.folder_label=QLabel()
        self.folder_label.setFont(label_font)
        self.has_ie_polys_count_label = QLabel()

        #==Information column (folder name, file name, number of tags)
        info_frame_layout = QHBoxLayout()
        info_frame_layout.setContentsMargins(0,0,0,0)
        info_frame_layout.addWidget ( self.folder_label, alignment=Qt.AlignCenter)
        info_frame_layout.addWidget (self.filename_label, alignment=Qt.AlignCenter)
        info_frame_layout.addWidget (self.has_ie_polys_count_label, alignment=Qt.AlignCenter)
        info_frame = QFrame()
        info_frame.setLayout(info_frame_layout)

        #--status line
        self.tips_label=QLabel()
        self.autosave_checkbox=QCheckBox()
        self.autosave_checkbox.setText(QStringDB.AutoConvertMaskText)
        self.autosave_checkbox.setChecked(False)
        status_frame_layout = QHBoxLayout()
        status_frame_layout.addWidget ( self.autosave_checkbox, alignment=Qt.AlignLeft)
        status_frame_layout.addWidget ( self.tips_label, alignment=Qt.AlignLeft)
        status_frame = QFrame()
        status_frame.setLayout(status_frame_layout)

        #---general interface
        main_canvas_layout = QVBoxLayout()
        main_canvas_layout.setContentsMargins(0,0,0,0)
        main_canvas_layout.addWidget (image_operate_bar_frame)
        main_canvas_layout.addWidget (info_frame)
        main_canvas_layout.addWidget (self.canvas)      
        main_canvas_layout.addWidget (status_frame)
        main_canvas_layout.addWidget (preview_image_bar)

        self.main_canvas_frame = QFrame()
        self.main_canvas_frame.setLayout(main_canvas_layout)
         
        self.main_l = QHBoxLayout()
        self.main_l.setContentsMargins(0,0,0,0)
        self.main_l.addWidget (self.main_canvas_frame)

        self.setLayout(self.main_l)

        geometry = self.cfg_dict.get('geometry', None)
        if geometry is not None:
            self.restoreGeometry(geometry)
        else:
            self.move( QPoint(0,0))


    def get_has_ie_polys_count(self):
        return self.has_ie_polys_count

    def set_has_ie_polys_count(self, c):
        self.has_ie_polys_count = c
        self.has_ie_polys_count_label.setText(f"{c} {QStringDB.labeled_tip}")

    def resizeEvent(self, ev):
        if self.loading_frame is not None:
            self.loading_frame.resize( ev.size() )
        if self.help_frame is not None:
            self.help_frame.resize( ev.size() )

def start(input_dirpath):
    """
    returns exit_code
    """
    io.log_info("Running XSeg editor.")

    if PackedFaceset.path_contains(input_dirpath):
        io.log_info (f'\n{input_dirpath} contains packed faceset! Unpack it first.\n')
        return 1

    root_path = Path(__file__).parent
    cfg_root_path = Path(tempfile.gettempdir())

    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)

    app = QApplication([])
    app.setApplicationName("XSegEditor")
    app.setStyle('Fusion')

    QFontDatabase.addApplicationFont( str(root_path / 'gfx' / 'fonts' / 'NotoSans-Medium.ttf') )

    app.setFont( QFont('NotoSans'))

    QUIConfig.initialize()
    QStringDB.initialize()

    QIconDB.initialize( root_path / 'gfx' / 'icons' )
    QCursorDB.initialize( root_path / 'gfx' / 'cursors' )
    QImageDB.initialize( root_path / 'gfx' / 'images' )

    app.setWindowIcon(QIconDB.app_icon)
    app.setPalette( QDarkPalette() )

    win = MainWindow( input_dirpath=input_dirpath, cfg_root_path=cfg_root_path)

    win.show()
    win.raise_()

    app.exec_()
    return 0