示例#1
0
class PropertyEditor(QWidget):
    # Signals
    insertionModeStarted = pyqtSignal(str)
    insertionModeEnded = pyqtSignal()
    insertionPropertiesChanged = pyqtSignal(object)
    editPropertiesChanged = pyqtSignal(object)

    def __init__(self, config, parent=None):
        QWidget.__init__(self, parent)
        self._class_config = {}
        self._class_items = {}
        self._class_prototypes = {}
        self._attribute_handlers = {}
        self._handler_factory = AttributeHandlerFactory()

        self._setupGUI()

        # Add label classes from config
        for label in config:
            self.addLabelClass(label)

    def onModelChanged(self, new_model):
        attrs = set([
            k for k, v in self._attribute_handlers.items()
            if v.autoAddEnabled()
        ])
        if len(attrs) > 0:
            start = time.time()
            attr2vals = {}
            for item in new_model.iterator(AnnotationModelItem):
                for attr in attrs:
                    if attr in item:
                        if attr not in attr2vals:
                            attr2vals[attr] = set((item[attr], ))
                        else:
                            attr2vals[attr] |= set((item[attr], ))
            diff = time.time() - start
            LOG.info("Extracted annotation values from model in %.2fs" % diff)
            for attr, vals in attr2vals.items():
                h = self._attribute_handlers[attr]
                for val in vals:
                    h.addValue(val, True)

    def addLabelClass(self, label_config):
        # Check label configuration
        if 'attributes' not in label_config:
            raise ImproperlyConfigured("Label with no 'attributes' dict found")
        attrs = label_config['attributes']
        if 'class' not in attrs:
            raise ImproperlyConfigured("Labels must have an attribute 'class'")
        label_class = attrs['class']
        if label_class in self._class_config:
            raise ImproperlyConfigured(
                "Label with class '%s' defined more than once" % label_class)

        # Store config
        self._class_config[label_class] = label_config

        # Parse configuration and create handlers and item
        self.parseConfiguration(label_class, label_config)

        # Add label class button
        button_text = label_config['text']
        button = QPushButton(button_text, self)
        button.setCheckable(True)
        button.setFlat(True)
        button.clicked.connect(bind(self.onClassButtonPressed, label_class))
        self._class_buttons[label_class] = button
        self._classbox_layout.addWidget(button)

        # Add hotkey
        if 'hotkey' in label_config:
            hotkey = QShortcut(QKeySequence(label_config['hotkey']), self)
            hotkey.activated.connect(button.click)
            self._class_shortcuts[label_class] = hotkey

    def parseConfiguration(self, label_class, label_config):
        attrs = label_config['attributes']

        # Add prototype item for insertion
        self._class_items[label_class] = AnnotationModelItem(
            {'class': label_class})

        # Create attribute handler widgets or update their values
        for attr, vals in attrs.items():
            if attr in self._attribute_handlers:
                self._attribute_handlers[attr].updateValues(vals)
            else:
                handler = self._handler_factory.create(attr, vals)
                if handler is None:
                    self._class_items[label_class][attr] = vals
                else:
                    self._attribute_handlers[attr] = handler

        for attr in attrs:
            if attr in self._attribute_handlers:
                self._class_items[label_class].update(
                    self._attribute_handlers[attr].defaults())

    def getHandler(self, attribute):
        if attribute in self._attribute_handlers:
            return self._attribute_handlers[attribute]
        else:
            return None

    def getLabelClassAttributes(self, label_class):
        return self._class_config[label_class]['attributes'].keys()

    def onClassButtonPressed(self, label_class):
        if self._class_buttons[label_class].isChecked():
            self.startInsertionMode(label_class)
        else:
            self.endInsertionMode()

    def startInsertionMode(self, label_class):
        self.endInsertionMode(False)
        for lc, button in self._class_buttons.items():
            button.setChecked(lc == label_class)
        LOG.debug("Starting insertion mode for %s" % label_class)
        self._label_editor = LabelEditor([self._class_items[label_class]],
                                         self, True)
        self._layout.insertWidget(1, self._label_editor, 0)
        self.insertionModeStarted.emit(label_class)

    def endInsertionMode(self, uncheck_buttons=True):
        if self._label_editor is not None:
            LOG.debug("Ending insertion/edit mode")
            self._label_editor.hide()
            self._layout.removeWidget(self._label_editor)
            self._label_editor = None
            if uncheck_buttons:
                self.uncheckAllButtons()
            self.insertionModeEnded.emit()

    def uncheckAllButtons(self):
        for lc, button in self._class_buttons.items():
            button.setChecked(False)

    def markEditButtons(self, label_classes):
        for lc, button in self._class_buttons.items():
            button.setFlat(lc not in label_classes)

    def currentEditorProperties(self):
        if self._label_editor is None:
            return None
        else:
            return self._label_editor.currentProperties()

    def startEditMode(self, model_items):
        # If we're in insertion mode, ignore empty edit requests
        if self._label_editor is not None and self._label_editor.insertionMode() \
                and len(model_items) == 0:
            return

        self.endInsertionMode()
        LOG.debug("Starting edit mode for items: %s" % model_items)
        self._label_editor = LabelEditor(model_items, self)
        self.markEditButtons(self._label_editor.labelClasses())
        self._layout.insertWidget(1, self._label_editor, 0)

    def _setupGUI(self):
        self._class_buttons = {}
        self._class_shortcuts = {}
        self._label_editor = None

        # Label class buttons
        self._classbox = QGroupBox("Labels", self)
        self._classbox_layout = FloatingLayout()
        self._classbox.setLayout(self._classbox_layout)

        # Global widget
        self._layout = MyVBoxLayout()
        self.setLayout(self._layout)
        self._layout.addWidget(self._classbox, 0)
        self._layout.addStretch(1)
示例#2
0
class PropertyEditor(QWidget):
    # Signals
    insertionModeStarted       = pyqtSignal(str)
    insertionModeEnded         = pyqtSignal()
    insertionPropertiesChanged = pyqtSignal(object)
    editPropertiesChanged      = pyqtSignal(object)

    def __init__(self, config, parent=None):
        QWidget.__init__(self, parent)
        self._class_config       = {}
        self._class_items        = {}
        self._class_prototypes   = {}
        self._attribute_handlers = {}
        self._handler_factory    = AttributeHandlerFactory()

        self._setupGUI()

        # Add label classes from config
        for label in config:
            self.addLabelClass(label)

    def onModelChanged(self, new_model):
        attrs = set([k for k, v in self._attribute_handlers.items() if v.autoAddEnabled()])
        if len(attrs) > 0:
            start = time.time()
            attr2vals = {}
            for item in new_model.iterator(AnnotationModelItem):
                for attr in attrs:
                    if attr in item:
                        if attr not in attr2vals:
                            attr2vals[attr] = set((item[attr], ))
                        else:
                            attr2vals[attr] |= set((item[attr], ))
            diff = time.time() - start
            LOG.info("Extracted annotation values from model in %.2fs" % diff)
            for attr, vals in attr2vals.items():
                h = self._attribute_handlers[attr]
                for val in vals:
                    h.addValue(val, True)

    def addLabelClass(self, label_config):
        # Check label configuration
        if 'attributes' not in label_config:
            raise ImproperlyConfigured("Label with no 'attributes' dict found")
        attrs = label_config['attributes']
        if 'class' not in attrs:
            raise ImproperlyConfigured("Labels must have an attribute 'class'")
        label_class = attrs['class']
        if label_class in self._class_config:
            raise ImproperlyConfigured("Label with class '%s' defined more than once" % label_class)

        # Store config
        self._class_config[label_class] = label_config

        # Parse configuration and create handlers and item
        self.parseConfiguration(label_class, label_config)

        # Add label class button
        button = QPushButton(label_class, self)
        button.setCheckable(True)
        button.setFlat(True)
        button.clicked.connect(bind(self.onClassButtonPressed, label_class))
        self._class_buttons[label_class] = button
        self._classbox_layout.addWidget(button)

        # Add hotkey
        if 'hotkey' in label_config:
            hotkey = QShortcut(QKeySequence(label_config['hotkey']), self)
            hotkey.activated.connect(button.click)
            self._class_shortcuts[label_class] = hotkey

    def parseConfiguration(self, label_class, label_config):
        attrs = label_config['attributes']

        # Add prototype item for insertion
        self._class_items[label_class] = AnnotationModelItem({ 'class': label_class })

        # Create attribute handler widgets or update their values
        for attr, vals in attrs.items():
            if attr in self._attribute_handlers:
                self._attribute_handlers[attr].updateValues(vals)
            else:
                handler = self._handler_factory.create(attr, vals)
                if handler is None:
                    self._class_items[label_class][attr] = vals
                else:
                    self._attribute_handlers[attr] = handler

        for attr in attrs:
            if attr in self._attribute_handlers:
                self._class_items[label_class].update(self._attribute_handlers[attr].defaults())

    def getHandler(self, attribute):
        if attribute in self._attribute_handlers:
            return self._attribute_handlers[attribute]
        else:
            return None

    def getLabelClassAttributes(self, label_class):
        return self._class_config[label_class]['attributes'].keys()

    def onClassButtonPressed(self, label_class):
        if self._class_buttons[label_class].isChecked():
            self.startInsertionMode(label_class)
        else:
            self.endInsertionMode()

    def startInsertionMode(self, label_class):
        self.endInsertionMode(False)
        for lc, button in self._class_buttons.items():
            button.setChecked(lc == label_class)
        LOG.debug("Starting insertion mode for %s" % label_class)
        self._label_editor = LabelEditor([self._class_items[label_class]], self, True)
        self._layout.insertWidget(1, self._label_editor, 0)
        self.insertionModeStarted.emit(label_class)

    def endInsertionMode(self, uncheck_buttons=True):
        if self._label_editor is not None:
            LOG.debug("Ending insertion/edit mode")
            self._label_editor.hide()
            self._layout.removeWidget(self._label_editor)
            self._label_editor = None
            if uncheck_buttons:
                self.uncheckAllButtons()
            self.insertionModeEnded.emit()

    def uncheckAllButtons(self):
        for lc, button in self._class_buttons.items():
            button.setChecked(False)

    def markEditButtons(self, label_classes):
        for lc, button in self._class_buttons.items():
            button.setFlat(lc not in label_classes)

    def currentEditorProperties(self):
        if self._label_editor is None:
            return None
        else:
            return self._label_editor.currentProperties()

    def startEditMode(self, model_items):
        # If we're in insertion mode, ignore empty edit requests
        if self._label_editor is not None and self._label_editor.insertionMode() \
                and len(model_items) == 0:
            return

        self.endInsertionMode()
        LOG.debug("Starting edit mode for items: %s" % model_items)
        self._label_editor = LabelEditor(model_items, self)
        self.markEditButtons(self._label_editor.labelClasses())
        self._layout.insertWidget(1, self._label_editor, 0)

    def _setupGUI(self):
        self._class_buttons = {}
        self._class_shortcuts = {}
        self._label_editor  = None

        # Label class buttons
        self._classbox = QGroupBox("Labels", self)
        self._classbox_layout = FloatingLayout()
        self._classbox.setLayout(self._classbox_layout)

        # Global widget
        self._layout = MyVBoxLayout()
        self.setLayout(self._layout)
        self._layout.addWidget(self._classbox, 0)
        self._layout.addStretch(1)
示例#3
0
class PropertyEditor(QWidget):
    # Signals
    insertionModeStarted = pyqtSignal(str, str, str)
    insertionModeEnded = pyqtSignal()
    insertionPropertiesChanged = pyqtSignal(object)
    editPropertiesChanged = pyqtSignal(object)
    checkboxStateChangedSignal = pyqtSignal(str, object)
    _optionsCheckboxStateChangedSignal = pyqtSignal(str, object)

    def setAnnotationScene(self, annotationScene):
        self._scene = annotationScene

    def setOptionStateChangedSignalSlot(self, checkboxStateChangedSignalSlot):
        if checkboxStateChangedSignalSlot is not None:
            self.checkboxStateChangedSignal.connect(
                checkboxStateChangedSignalSlot)
        self._optionsCheckboxStateChangedSignal.connect(
            self.onInserterButtonOptionChanged)

    # set enable status for the checkbox group which is attached to inserterButton
    def enableCheckboxGroup(self, inserterButtonName, enabled=True):
        widgetsInfo = self._widgets_dict.get(inserterButtonName, None)
        if not widgetsInfo: return
        if widgetsInfo[1]:
            # this button has an attachedCheckboxGroup
            attachedCheckboxGroupWidget = widgetsInfo[1]
            attachedCheckboxGroupWidget.enableAll(enabled)

    # set check status for one specific checkbox which is attached to inserterButton
    def setCheckedCheckbox(self,
                           inserterButtonName,
                           checkBoxName,
                           checked=True):
        widgetsInfo = self._widgets_dict.get(inserterButtonName, None)
        # print "inserterButtonName {} _widgets_dict {} checkBoxName {} checked {} ...".format(inserterButtonName, self._widgets_dict, checkBoxName, checked)
        if not widgetsInfo: return
        if widgetsInfo[1]:
            # this button has an attachedCheckboxGroup
            attachedCheckboxGroupWidget = widgetsInfo[1]
            attachedCheckboxGroupWidget.setCheckedCheckbox(
                checkBoxName, checked)

    # set check status for some specific checkboxs which are attached to inserterButton
    def setCheckedCheckboxs(self,
                            inserterButtonName,
                            checkBoxNamesList,
                            checked=True):
        widgetsInfo = self._widgets_dict.get(inserterButtonName, None)
        if not widgetsInfo: return
        if widgetsInfo[1]:
            # this button has an attachedCheckboxGroup
            attachedCheckboxGroupWidget = widgetsInfo[1]
            attachedCheckboxGroupWidget.setCheckedCheckboxs(
                checkBoxNamesList, checked)

    # get names list of all checkboxs in the checkbox group which is attached to inserterButton
    def getCheckboxsName(self, inserterButtonName, enabled=True):
        widgetsInfo = self._widgets_dict.get(inserterButtonName, None)
        if not widgetsInfo: return
        if widgetsInfo[1]:
            # this button has an attachedCheckboxGroup
            attachedCheckboxGroupWidget = widgetsInfo[1]
            return attachedCheckboxGroupWidget.get_checkboxs_name()
        return None

    def __init__(self, config, idName, displayName, groupBoxName, parent=None):
        QWidget.__init__(self, parent)
        self._inserters_modelitems = {
        }  # dict. { inserter_class_name : AnnotationModelItem, ...}. Note that, inserter_class_name is not always equal to inserter_button_name, for those buttons with attached options.
        self._inserters_guiitems = {
        }  # dict. { inserter_class_name : (inserter_creator_method, inserted_item_creator_method) }
        self._idName = idName
        self._groupBoxName = groupBoxName
        self._displayName = displayName
        self._scene = None
        self._current_toinsert_inserterClassName = None
        self._current_is_insert_mode = False
        self._widgets_dict = {
        }  # dict. { inserter_button_name :[buttonWidget, buttonAttachedOptionsWidget, buttonName], ....}

        self._setupGUI(self._groupBoxName)

        # Add label classes from config
        for buttonConfig in config:
            self.addButton(buttonConfig)

        # print "self._inserters_guiitems = {}".format(self._inserters_guiitems)

    def onModelChanged(self, new_model):
        pass

    def addButton(self, buttonConfig):
        # LOG.info("addLabelClass with buttonConfig {} ...".format(buttonConfig))
        # Check label configuration
        if 'attributes' not in buttonConfig:
            raise ImproperlyConfigured("Label with no 'attributes' dict found")

        inserter_creator_method = buttonConfig['inserter']
        inserted_item_creator_method = buttonConfig['item']

        attrs = buttonConfig['attributes']
        # LOG.info("buttonConfig['attributes'] {} type {} ...".format(buttonConfig['attributes'], type(buttonConfig['attributes'])))
        if config.METADATA_LABELCLASS_TOKEN not in attrs:
            raise ImproperlyConfigured(
                "Labels must have an attribute config.METADATA_LABELCLASS_TOKEN"
            )
        label_class = attrs[config.METADATA_LABELCLASS_TOKEN]
        # LOG.info("buttonConfig['attributes'][config.METADATA_LABELCLASS_TOKEN] {} type {} ...".format(attrs[config.METADATA_LABELCLASS_TOKEN], type(attrs[config.METADATA_LABELCLASS_TOKEN])))
        if label_class in self._inserters_modelitems:
            raise ImproperlyConfigured(
                "Label with class '%s' defined more than once" % label_class)

        # Add INSERTER button
        displaytext = attrs['displaytext']
        buttonName = label_class
        button = QPushButton(displaytext, self)

        optionInfo = attrs.get('optioninfo', None)
        # print "button {}: option {}".format(buttonName, optionInfo)
        buttonOptionsWidget = None
        buttonDisplayColor = None
        tmp = [
            o.get(default_config.METADATA_DISPLAYCOLOR_TOKEN, None)
            for o in optionInfo['option']
            if o.get(config.METADATA_IS_DEFAULT_TOKEN, False)
        ][0] if optionInfo else None
        buttonDisplayColor = tmp if tmp else optionInfo['option'][0].get(
            default_config.METADATA_DISPLAYCOLOR_TOKEN,
            None) if optionInfo else attrs.get(
                default_config.METADATA_DISPLAYCOLOR_TOKEN, None)

        # LOG.info(u"buttonConfig['attributes'] = {}, displaytext = {}, displayColor = {}".format(attrs, displaytext, buttonDisplayColor))

        # ==== zx add @ 20161114 to display button with color configured by user ====
        txtColor = None
        if buttonDisplayColor is not None:
            qtColor, hexColor = utils.getColorDesc(buttonDisplayColor)
            rgba = utils.hexColorStrToRGBA(hexColor)
            distance = math.sqrt((rgba[0] - 255)**2 + (rgba[1] - 255)**2 +
                                 (rgba[2] - 255)**2)
            txtColor = '#000000' if distance > config.GUI_COLOR_TAG_TEXT_BLACKWHITE_TOGGLE_THRESHOLD else '#ffffff'
            buttonDisplayColor = hexColor[0:8]
            # LOG.info(u"buttonDisplayColor = {} txtColor = {}, qtColor = {} hexColor = {}".format(buttonDisplayColor, txtColor, qtColor, hexColor ))
            # print (u"buttonDisplayColor = {} txtColor = {}, qtColor = {} hexColor = {}".format(buttonDisplayColor, txtColor, qtColor, hexColor ))

        # print "button {} buttonDisplayColor {} ...".format(buttonName, buttonDisplayColor)
        utils.set_qobj_stylesheet(
            button,
            'QPushButton',
            widgetBackgroundColor=None,
            widgetTextColor=None,
            widgetBackgroundColorWhenChecked=buttonDisplayColor,
            widgetTextColorWhenChecked=txtColor)
        # ========================== zx add end ============================

        button.clicked.connect(bind(self.onClassButtonPressed, label_class))
        # Add hotkey
        if 'hotkey' in buttonConfig:
            hotkey = QShortcut(QKeySequence(buttonConfig['hotkey']), self)
            hotkey.activated.connect(button.click)
            self._class_shortcuts[label_class] = hotkey
            # print "{} is set hotkey {} {}".format(label_class, buttonConfig['hotkey'], hotkey)

        if optionInfo:
            optionListName = optionInfo['name']
            optionListText = optionInfo['displaytext']
            option = optionInfo['option']
            buttonOptionsWidget = AttachedCheckboxGroupWidget(
                buttonName,
                optionListName,
                optionListText,
                True,
                option,
                self._optionsCheckboxStateChangedSignal,
                parent=None)

            isDefaultOption = False
            for o in option:
                new_class = o.get('tag', None)
                if new_class:
                    # Add prototype mdoelItem for insertion
                    mi = {config.METADATA_LABELCLASS_TOKEN: new_class}
                    mi['displaytext'] = o.get('displaytext', new_class)

                    self._inserters_modelitems[
                        new_class] = AnnotationModelItem(mi)
                    self._inserters_guiitems[new_class] = (
                        inserter_creator_method, inserted_item_creator_method)
                    # print "addButton.....self._inserters_guiitems[{}] = {}".format(new_class, (inserter_creator_method, inserted_item_creator_method))
                    for key, val in o.iteritems():
                        if key != 'tag':
                            self._inserters_modelitems[new_class][key] = val

        else:
            attrs = buttonConfig['attributes']

            # Add prototype mdoelItem for insertion
            mi = {config.METADATA_LABELCLASS_TOKEN: label_class}
            mi['displaytext'] = attrs.get('displaytext', label_class)

            self._inserters_modelitems[label_class] = AnnotationModelItem(mi)
            self._inserters_guiitems[label_class] = (
                inserter_creator_method, inserted_item_creator_method)

            # update their values
            for key, val in attrs.iteritems():
                self._inserters_modelitems[label_class][key] = val
                # LOG.info("self._inserters_modelitems[{}][{}] = {}".format(label_class, key, val))

        self._widgets_dict[label_class] = [button, buttonOptionsWidget]
        # print "self._widgets_dict [ {} ] = {}".format(label_class, button)

        button.setCheckable(True)

        utils.set_qobj_stylesheet(
            self._widgets_dict[label_class][0],
            'QPushButton',
            widgetBackgroundColor=None,
            widgetTextColor=None,
            widgetBackgroundColorWhenChecked=buttonDisplayColor,
            widgetTextColorWhenChecked=txtColor)

        if buttonOptionsWidget:
            # self._layout.addWidget(button)
            self._inserterButtonGroup_layout.addWidget(button)
            self._layout.addWidget(buttonOptionsWidget)
        else:
            self._inserterButtonGroup_layout.addWidget(button)

    def onClassButtonPressed(self, pressedButtonName):
        # print "onClassButtonPressed ... button {} isChecked !".format(pressedButtonName)
        inserterClassName = pressedButtonName

        if pressedButtonName not in self._widgets_dict.keys():
            # check whether passed-in pressedButtonName argument is an checkbox option name
            for kk, vv in self._widgets_dict.iteritems():
                buttonOptionsWidget = vv[1] if vv else None
                if buttonOptionsWidget:
                    optionsName = buttonOptionsWidget.get_checkboxs_name()
                    if pressedButtonName in optionsName:
                        pressedButtonName = kk
                        # print "pressedButtonName ============ {}".format(kk)

        if self._widgets_dict[pressedButtonName][0].isChecked():
            if not self._scene._image_item:
                self._widgets_dict[pressedButtonName][0].setChecked(False)
                return

            checkedOption = None
            if self._widgets_dict[pressedButtonName][1]:
                buttonOptionsWidget = self._widgets_dict[pressedButtonName][1]
                checkedOptionsNameList = buttonOptionsWidget.get_checked_widgets_name(
                )
                if not checkedOptionsNameList:
                    checkedOptionsNameList = [
                        buttonOptionsWidget._defaultCheckboxName
                    ]
                    buttonOptionsWidget.setCheckedCheckbox(
                        buttonOptionsWidget._defaultCheckboxName)

                buttonOptionsWidget.enableAll()
                checkedOptionName = checkedOptionsNameList[0]

                orgClsNames = [
                    i for i in buttonOptionsWidget.get_checkboxs_name()
                    if i in self._inserters_modelitems.keys()
                ]
                if (not orgClsNames):
                    raise RuntimeError(
                        "There are no or more than one inserters")
                inserterClassName = checkedOptionName

            if ((not self._scene._labeltool._enableAutoConnectLabelMode)
                    and (self._scene._selectedDisplayItemsList is not None)
                    and (len(self._scene._selectedDisplayItemsList) == 1)):
                parentModelItem = self._scene._selectedDisplayItemsList[0][
                    annotationscene.
                    SELECTED_DISPLAYED_ITEMS_LIST_MODELITEM_INDEX]
                clsNameOfChild = inserterClassName
                if ((clsNameOfChild == config.ANNOTATION_PERSONBIKE_TOKEN) or
                    (clsNameOfChild == config.ANNOTATION_PEDESTRAIN_TOKEN) or
                    (clsNameOfChild == config.ANNOTATION_VEHICLE_TOKEN)):
                    self._scene._selectedDisplayItemsList = []
                    parentModelItem = None

            elif (self._scene._sceneViewMode == config.OBJ_VIEW_MODE) and (
                    self._scene._objViewModeTopModelItem is not None):
                parentModelItem = self._scene._objViewModeTopModelItem
            else:
                parentModelItem = None

            LOG.info(
                "onClassButtonPressed ... self._scene._labeltool._enableAutoConnectLabelMode = {} parentModelItem = {}"
                .format(self._scene._labeltool._enableAutoConnectLabelMode,
                        parentModelItem))
            if not self._scene._labeltool._enableAutoConnectLabelMode:

                clsNameOfChild = inserterClassName
                isValid, rectBoundary = self._scene.checkModelItemValidity(
                    clsNameOfChild,
                    None,
                    parentModelItem,
                    self._scene._image_item,
                    enablePopupMsgBox=True,
                    enableEmitStatusMsg=False,
                    enableCountingThisModelItem=True)

                if not isValid:
                    LOG.info("enter hhhhhhhhhhhhhhh....")

                    # --------------------------------------
                    # added by zx @ 2017-02-08
                    # to exit all inserters among all pannels
                    # --------------------------------------
                    for i in self._scene._labeltool.propertyeditors():
                        if i:
                            i.setCheckedAll(False)
                    # self._scene._labeltool.exitInsertMode()
                    self._scene.deselectAllItems()
                    # --------------------------------------

                    return

            LOG.info("onClassButtonPressed ... call startInsertionMode...")

            self.startInsertionMode(pressedButtonName, inserterClassName)

        else:

            LOG.info("onClassButtonPressed ... call endInsertionMode...")
            self.endInsertionMode()

        return

    def startInsertionMode(self, pressedButtonName, inserterClassName):
        self.endInsertionMode(False)
        LOG.info(
            "Starting insertion mode for {} .. self._inserters_modelitems[{}]={} "
            .format(inserterClassName, inserterClassName,
                    self._inserters_modelitems[inserterClassName]))

        for lc, buttonAndOption in self._widgets_dict.items():
            buttonAndOption[0].setChecked(lc == pressedButtonName)
            if buttonAndOption[1]:
                # print ("startInsertionMode .. setchecked for {} option {} enabled = {} ".format(lc, buttonAndOption[1], (lc == pressedButtonName)))
                buttonAndOption[1].enableAll(lc == pressedButtonName)

            LOG.info(
                "startInsertionMode .. setchecked for {} button checked = {} ".
                format(lc, lc == inserterClassName))
            # print ("startInsertionMode .. setchecked for {} button {} checked = {} ".format(lc, buttonAndOption[0], lc == inserterClassName))

        self._current_toinsert_inserterClassName = inserterClassName
        # print "==== startInsertionMode set _current_is_insert_mode False ..."
        self._current_is_insert_mode = True

        LOG.info(
            u"startInsertionMode .. emit insertionModeStarted(pannel = {}, inserter = {})... "
            .format(self._idName, inserterClassName))

        self.insertionModeStarted.emit(self._idName, inserterClassName,
                                       inserterClassName)

    def endInsertionMode(self, uncheck_buttons=True):
        if uncheck_buttons:
            self.setCheckedAll(False)

        LOG.info(
            u"endInsertionMode... PropertyEditor {} endInsertionMode(uncheck_buttons = {})"
            .format(self._displayName, uncheck_buttons))
        # print (u"endInsertionMode... PropertyEditor {} endInsertionMode(uncheck_buttons = {})".format(self._displayName, uncheck_buttons))
        self._current_is_insert_mode = False
        self.insertionModeEnded.emit()

    def enableAll(self, enabled=True):
        for v, buttonAndOption in self._widgets_dict.items():
            buttonAndOption[0].setEnabled(enabled)

    def setCheckedAll(self, checked=True):
        for v, buttonAndOption in self._widgets_dict.items():
            buttonAndOption[0].setChecked(checked)

    def getChecked(self):
        buttonname = None
        for buttonName, buttonWidgets in self._widgets_dict.iteritems():
            if buttonWidgets[0].isChecked():
                return buttonName
        return buttonname

    def setChecked(self, buttonName, checked=True):
        buttonWidget = self._widgets_dict.get(buttonName, None)
        if buttonWidget is not None:
            buttonWidget[0].setChecked(checked)

    def enable(self, buttonName, enabled=True):
        buttonWidget = self._widgets_dict.get(buttonName, None)
        if buttonWidget is not None:
            buttonWidget[0].setEnabled(enabled)

    def markEditButtons(self, buttonNamesList):
        for lc, buttonAndOption in self._widgets_dict.items():
            # buttonAndOption[0].setFlat(lc not in buttonNamesList)
            buttonAndOption[0].setChecked(lc in buttonNamesList)

    def updateCurrentInserter(self, inserterClassName):
        self._current_toinsert_inserterClassName = inserterClassName
        return

    def isInsertingMode(self):
        return self._current_is_insert_mode

    def currentToInsertItemProperty(self):
        return self._inserters_modelitems.get(
            self._current_toinsert_inserterClassName,
            {}) if self._current_toinsert_inserterClassName else {}

    # return [inserter0ClassName, inserter1ClassName, ...]
    def getSupportedInserters(self):
        return self._inserters_modelitems.keys()

    def startEditMode(self, model_items):
        # If we're in insertion mode, ignore empty edit requests
        if self._current_is_insert_mode and len(model_items) == 0:
            return

        self.endInsertionMode()
        LOG.info("Starting edit mode for model_items: {} ".format(model_items))
        self._current_toinsert_inserterClassName = None
        # print "==== startEditMode set _current_is_insert_mode False ..."
        self._current_is_insert_mode = False

        labelClasses = set([
            item[config.METADATA_LABELCLASS_TOKEN] for item in model_items
            if config.METADATA_LABELCLASS_TOKEN in item
        ])
        self.markEditButtons(labelClasses)

    def _setupGUI(self, _groupBoxName):
        self._widgets_dict = {}
        self._class_shortcuts = {}

        # Label class buttons
        qss = "QGroupBox::title {subcontrol-origin: margin; subcontrol-position: top left; padding: 0 3px; color : red; font-weight:bold; }"
        self._inserterButtonGroupbox = QGroupBox(_groupBoxName, self)
        self._inserterButtonGroupbox.setStyleSheet(qss)
        self._inserterButtonGroup_layout = FloatingLayout()
        self._inserterButtonGroupbox.setLayout(
            self._inserterButtonGroup_layout)

        # Global widget
        self._layout = MyVBoxLayout()
        self.setLayout(self._layout)
        self._layout.addWidget(self._inserterButtonGroupbox, 0)
        self._layout.addStretch(1)

    def onInserterButtonOptionChanged(self, _inserterButtonName,
                                      checkboxsInfo):
        # print "onInserterButtonOptionChanged {}: inserterButton {} checkboxinfo {}".format(self._idName, _inserterButtonName, checkboxsInfo)
        if not checkboxsInfo:
            return
        if len(checkboxsInfo) != 2:
            return

        inserterButtonName = str(_inserterButtonName)

        checkedWidgetsAttrDescDict = checkboxsInfo[0]
        allCheckboxsNameList = checkboxsInfo[1]

        if len(checkedWidgetsAttrDescDict) != 1:
            return

        checkedOptionName = checkedWidgetsAttrDescDict.keys()[0]

        if (self._current_toinsert_inserterClassName == checkedOptionName):
            return

        checkedOptionDisplayText = checkedWidgetsAttrDescDict.values()[0][
            attrArea.checkedWidgetsAttrDescList_checkedWidgetText_idx]
        checkedOptionDisplayColor = checkedWidgetsAttrDescDict.values()[0][
            attrArea.checkedWidgetsAttrDescList_checkedWidgetColor_idx]

        # change inserter button displaycolor
        # print "--- self._current_is_insert_mode = {} self._widgets_dict[{}][0].is_checked() = {} ...".format(self._current_is_insert_mode, inserterButtonName, self._widgets_dict[inserterButtonName][0].isChecked())
        utils.set_qobj_stylesheet(
            self._widgets_dict[inserterButtonName][0],
            'QPushButton',
            widgetBackgroundColor=None,
            widgetTextColor=None,
            widgetBackgroundColorWhenChecked=checkedOptionDisplayColor,
            widgetTextColorWhenChecked=None)

        if not self._current_is_insert_mode or self._scene._sceneViewMode == config.OBJ_VIEW_MODE:
            parentModelItem = None

            if self._scene._sceneViewMode == config.OBJ_VIEW_MODE:
                # print "_sceneViewMode = {} _objViewModeTopModelItem = {}".format(self._scene._sceneViewMode, self._scene._objViewModeTopModelItem)
                parentModelItem = self._scene._objViewModeTopModelItem

            if not parentModelItem:
                # get current scene's parent modelitem
                if len(self._scene._selectedDisplayItemsList) > 0:
                    parentModelItem = self._scene._selectedDisplayItemsList[0][
                        annotationscene.
                        SELECTED_DISPLAYED_ITEMS_LIST_MODELITEM_INDEX]
                    # print "_selectedDisplayItemsList = {}".format(self._scene._selectedDisplayItemsList)

            if not parentModelItem:
                parentModelItem = self._scene._labeltool._current_image

            # print ("checkedOptionName = {} parentModelItem = {}".format(checkedOptionName, GET_ANNOTATION(parentModelItem)))
            # print ("allCheckboxsNameList = {}".format((allCheckboxsNameList)))

            # find all modelItems which has class which in is the option list of changed inserter option but not has not that class of option
            foundNum = 0
            foundListList = []
            for a in allCheckboxsNameList:
                foundList = model.findModelItemsWithSpecificClassNameFromRoot(
                    a, parentModelItem, 5)
                # print "find {} => {}".format(a, foundList)
                foundNum += len(foundList)  # if a != checkedOptionName else 0
                maxNum = 1
                if foundNum > maxNum:
                    print "Error: there are {} items of {} in current image! We can only modify at least {} item once!".format(
                        foundNum, allCheckboxsNameList, maxNum)
                    foundListList = []
                    break
                if a != checkedOptionName:
                    foundListList += foundList

            # print ("foundListList = {}".format((foundListList)))
            # update found modelitems' class field to current selected option
            if foundListList:
                for found in foundListList:
                    if not found: continue
                    modelItem = found[0]
                    iterativeLevelNum = found[1]
                    # print "ModelItem is changed from {} => {}!".format(modelItem.get_label_name(), checkedOptionName)
                    modelItem.update({'class': checkedOptionName},
                                     needEmitDatachanged=True)
                    sceneItem = self._scene.modelItem_to_baseItem(modelItem)

                    # print u"checkedOptionDisplayText = {} checkedOptionDisplayColor = {}".format(checkedOptionDisplayText, checkedOptionDisplayColor)
                    sceneItem.updateInfo(checkedOptionDisplayText,
                                         checkedOptionDisplayColor)

                    # newModelItem = copy.deepcopy(oldModelItem)
                    # self._scene.deleteItems(self, [oldModelItem], recursiuve=True)
        else:

            # switch to new inserter as per current selected inserter button type
            # print "---------------- _current_is_insert_mode = {} ...".format(self._current_is_insert_mode)
            # print "call onInsertionModeStarted with {} {}...".format(checkedOptionName, inserterButtonName)
            self.updateCurrentInserter(checkedOptionName)
            self._scene.onInsertionModeStarted(self._idName, checkedOptionName,
                                               inserterButtonName)

        return

    def onInsertionModeStarted(self, _classNameToInsert,
                               _buttonNameOfInserter):
        buttonNameOfInserter = str(_buttonNameOfInserter)
        self.setChecked(buttonNameOfInserter, True)
        self._current_is_insert_mode = True
        self.updateCurrentInserter(str(_classNameToInsert))
        return
示例#4
0
class AttrAreaWidget(QWidget):
    checkboxStateChangedSignal = pyqtSignal(str, object)

    def setAnnotationScene(self, annotationScene):
        self._scene = annotationScene

    def enableAllGroups(self, enabled=True):
        for gn, gw in self.checkboxGroupWidgets.items():
            gw.setEnabled(enabled)
            gw.enableAll(enabled)

    def enableCheckboxGroup(self, checkboxGroupName, enabled=True):
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            groupWidget.setEnabled(enabled)
            groupWidget.enableAll(enabled)

    def enableCheckbox(self, checkboxGroupName, checkboxName, enabled=True):
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            groupWidget.enable(checkboxName, enabled)

    # return (names_list, texts_list, displaycolors_list)
    def get_group_config(self, checkboxGroupName):
        names = []
        texts = []
        displaycolors = []
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            names, texts, displaycolors = groupWidget.get_config()

        return names, texts, displaycolors

    # return names_list
    def get_group_names(self):
        groupNames = []
        for groupName in self.checkboxGroupWidgets.keys():
            groupNames.append(groupName)
        return groupNames

    def setCheckedAllGroups(self, checked=True):
        for groupName, groupWidget in self.checkboxGroupWidgets.items():
            groupWidget.setCheckedAll(checked)

    def setCheckedGroup(self, checkboxGroupName, checked=True):
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            groupWidget.setCheckedAll(checked)

    def setCheckedCheckbox(self,
                           checkboxGroupName,
                           checkboxName,
                           checked=True):
        # print "checkboxGroupName {} checkboxName {} checked {} ...".format(checkboxGroupName, checkboxName, checked)
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        # print "checkboxGroupWidgets = {}...".format(self.checkboxGroupWidgets)
        if groupWidget is not None:
            # LOG.info("AttrAreaWidget.setCheckedCheckbox for group {} checked {} ..".format(checkboxGroupName, checked))
            # print ("AttrAreaWidget.setCheckedCheckbox for group {} checked {} ..".format(checkboxGroupName, checked))
            groupWidget.setCheckedCheckbox(checkboxName, checked)

    def setCheckedCheckboxs(self,
                            checkboxGroupName,
                            checkboxNamesList,
                            checked=True):
        if checkboxNamesList is None:
            return
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            # LOG.info(u"{} AttrAreaWidget.setCheckedCheckbox for group {} box {} checked {} ..".format(self.groupName, checkboxGroupName, checkboxNamesList, checked))
            groupWidget.setCheckedCheckboxs(checkboxNamesList, checked)

    def sendCheckedSignal(self, checkboxGroupName):
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            groupWidget.sendCheckedSignal()

    def getCheckedWidgetsOptionInfo(self, checkboxGroupName):
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            return groupWidget.getCheckedWidgetsOptionInfo()
        return None, None

    def setOptionStateChangedSignalSlot(self, checkboxStateChangedSignalSlot):
        if checkboxStateChangedSignalSlot is not None:
            # print u"pannel {}: checkboxStateChangedSignal{} checkboxStateChangedSignalSlot {} is connected...".format(self.idName, self.checkboxStateChangedSignal, checkboxStateChangedSignalSlot)
            self.checkboxStateChangedSignal.connect(
                checkboxStateChangedSignalSlot)

    def hideGroup(self, checkboxGroupName):
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            return groupWidget.hide()

    def __init__(self,
                 property=None,
                 idName=None,
                 displayName=None,
                 parent=None):
        QWidget.__init__(self, parent)

        self.hotkeys = []
        self.displayName = displayName
        self.idName = idName

        self.vlayout = MyVBoxLayout()  #QVBoxLayout()
        self.vlayout.setAlignment(Qt.AlignTop)
        self.vlayout.setSpacing(4)
        self.vlayout.setMargin(4)
        #self.vlayout.setContentsMargins(0, 0, 0, 44)

        # LOG.info("AttrAreaWidget constructor : property {} ...".format(property))
        self.numGroups = len(property) if property is not None else 0

        self.checkboxGroupsDescDict = {}
        self.checkboxGroupWidgets = {}

        for i in xrange(self.numGroups):
            thisGroupProperty = property[i]  # thisGroupProperty is a dict
            thisGroupName = thisGroupProperty.get(
                'name')  # thisGroupName is a string
            thisGroupOption = thisGroupProperty.get(
                'option'
            )  # thisGroupOption is a tuple of dicts. each dict describes a checkbox
            thisGroupText = thisGroupProperty.get('displaytext')

            isExclusive = False if thisGroupName in config.NON_EXCLUSIVE_ATTRS_TAG_LIST else True
            if thisGroupName == config.ANNOTATION_PERSONBIKE_TYPE_GROUP_TOKEN:
                checkboxWidgetMinWidthInPixels = 52
            else:
                checkboxWidgetMinWidthInPixels = 38

            if thisGroupName == config.ANNOTATION_UPPER_COLOR_TOKEN:
                thisGroup_maxCheckedNum = config.MAX_UPPER_COLOR_NUMBER
                thisGroup_maxSeperateWidgetNum = config.GUI_MAX_SEPERATE_UPPER_COLOR_WIDGET_NUMBER
            elif thisGroupName == config.ANNOTATION_LOWER_COLOR_TOKEN:
                thisGroup_maxCheckedNum = config.MAX_LOWER_COLOR_NUMBER
                thisGroup_maxSeperateWidgetNum = config.GUI_MAX_SEPERATE_LOWER_COLOR_WIDGET_NUMBER
            elif thisGroupName == config.ANNOTATION_VEHICLE_COLOR_TOKEN:
                thisGroup_maxCheckedNum = config.MAX_VEHICLE_COLOR_NUMBER
                thisGroup_maxSeperateWidgetNum = config.GUI_MAX_SEPERATE_VEHICLE_COLOR_WIDGET_NUMBER
            else:
                thisGroup_maxCheckedNum = config.MAX_CHECKED_OPTIONS_NUMBER
                thisGroup_maxSeperateWidgetNum = config.MAX_OPTIONS_NUMBER

            thisGroupWidget = CheckboxListWidget(
                thisGroupName, thisGroupText, isExclusive, thisGroupOption,
                self.checkboxStateChangedSignal, thisGroup_maxCheckedNum,
                thisGroup_maxSeperateWidgetNum)

            # add checkbox record to this checkbox group record
            thisGroupCheckboxs = {}
            for checkboxOption in thisGroupOption:
                thisCheckboxName = checkboxOption[
                    config.METADATA_ATTR_VALUE_TOKEN]
                thisCheckboxProperty = checkboxOption.copy()
                thisGroupCheckboxs[thisCheckboxName] = thisCheckboxProperty

            self.vlayout.addWidget(thisGroupWidget)

            self.checkboxGroupsDescDict[
                thisGroupName] = thisGroupCheckboxs  # add this checkbox group record to checkbox groups recrod
            self.checkboxGroupWidgets[thisGroupName] = thisGroupWidget

        # LOG.info("checkboxGroupWidgets = {} checkboxGroupsDescDict = {}".format(self.checkboxGroupWidgets, self.checkboxGroupsDescDict)

        self.vlayout.addStretch(1)
        self.setLayout(self.vlayout)
        return

    def stateHasChanged(self, checkedWidgetsAttrDescDict):
        # LOG.info("call AttrAreaWidget.stateHasChanged({})".format(checkedWidgetsAttrDescDict))
        return

    def get_checked_widgets_name(self, group_name):
        widget = self.checkboxGroupWidgets.get(str(group_name), None)
        # LOG.info("AttrAreaWidget.get_checked_widgets_name ... widget = {}".format(widget))
        checkedWidgetsNameList = widget.get_checked_widgets_name(
        ) if widget is not None else None
        # LOG.info("AttrAreaWidget.get_checked_widgets_name ... checkedWidget = {}".format(checkedWidget))
        return checkedWidgetsNameList

    def add_hotkey(self, choice, name, hotkey):
        self.hotkeys.append((choice, name, hotkey))

    def get_checked_widgets_attrDesc(self):
        descsDict = {}
        for widgetName, widgetInfo in self.checkboxWidgetsInfo.items():
            if widgetInfo[0].isChecked():
                desc = [None, None, None, None, None]
                desc[
                    checkedWidgetsAttrDescList_checkedWidget_idx] = widgetInfo[
                        0]
                desc[
                    checkedWidgetsAttrDescList_checkedWidgetText_idx] = widgetInfo[
                        1]
                desc[
                    checkedWidgetsAttrDescList_checkedWidgetColor_idx] = widgetInfo[
                        2]
                desc[
                    checkedWidgetsAttrDescList_checkedWidgetStyleSheet_idx] = widgetInfo[
                        3]
                descsDict[widgetName] = desc

        for widgetInfo in self.popupWidgetsInfo.values():
            for actionWidget in widgetInfo[
                    popupWidgetsInfo_actionsWidgetList_idx]:
                if actionWidget.isChecked():
                    idx = widgetInfo[
                        popupWidgetsInfo_actionsWidgetList_idx].index(
                            actionWidget)
                    desc = [None, None, None, None, None]
                    desc[
                        checkedWidgetsAttrDescList_checkedWidget_idx] = actionWidget
                    desc[
                        checkedWidgetsAttrDescList_checkedWidgetText_idx] = widgetInfo[
                            popupWidgetsInfo_actionsTextList_idx][idx]
                    desc[
                        checkedWidgetsAttrDescList_checkedWidgetColor_idx] = widgetInfo[
                            popupWidgetsInfo_actionsColorList_idx][idx]
                    desc[
                        checkedWidgetsAttrDescList_checkedWidgetStyleSheet_idx] = widgetInfo[
                            popupWidgetsInfo_actionsWidgetStyleSheet_idx]
                    desc[
                        checkedWidgetsAttrDescList_checkedWidgetPopupParentWidget_idx] = widgetInfo[
                            popupWidgetsInfo_pushButtonWidget_idx]
                    widgetName = widgetInfo[
                        popupWidgetsInfo_actionsNameList_idx][idx]
                    descsDict[widgetName] = desc

        return descsDict

    def startEditMode(self, model_items):
        return
class buttonWithOptionsWidget(QWidget):
    def __init__(self,
                 annotation_scene,
                 stateChangedSignalSlot=None,
                 buttonWithOptionsProperty=None,
                 displayName=None,
                 parent=None):
        QWidget.__init__(self, parent)
        # print "buttonWithOptionsProperty = {}".format(comboPannelProperty)
        self.buttonWithOptionsProperty = buttonWithOptionsProperty

        self.name = displayName

        self.vlayout = QVBoxLayout()
        self.vlayout.setAlignment(Qt.AlignTop)
        self.vlayout.setSpacing(4)
        self.vlayout.setMargin(4)
        #self.vlayout.setContentsMargins(0, 0, 0, 44)
        self.vlayout.addWidget(self.propertyEditorWidget)
        self.vlayout.addWidget(self.attrAreaWidget)
        self.vlayout.addStretch(1)
        self.setLayout(self.vlayout)
        return

    def enableCheckbox(self, checkboxGroupName, checkboxName, enabled=True):
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            groupWidget.enable(checkboxName, enabled)

    def setCheckedCheckbox(self, checkboxName, checked=True):
        self.checkboxGroupWidget.setCheckedCheckbox(checkboxName, checked)

    def setCheckedCheckboxs(self, checkboxNamesList, checked=True):
        if checkboxNamesList is None:
            return
        groupWidget = self.checkboxGroupWidget.get(checkboxGroupName, None)
        if groupWidget is not None:
            # LOG.info(u"{} AttrAreaWidget.setCheckedCheckbox for group {} box {} checked {} ..".format(self.name, checkboxGroupName, checkboxNamesList, checked))
            groupWidget.setCheckedCheckboxs(checkboxNamesList, checked)

    def sendCheckedSignal(self, checkboxGroupName):
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            groupWidget.sendCheckedSignal()

    def getCheckedWidgetsOptionInfo(self, checkboxGroupName):
        groupWidget = self.checkboxGroupWidgets.get(checkboxGroupName, None)
        if groupWidget is not None:
            return groupWidget.getCheckedWidgetsOptionInfo()
        return None, None

    def __init__(self,
                 stateChangedSignalSlot=None,
                 property=None,
                 displayName=None,
                 parent=None):
        QWidget.__init__(self, parent)

        self.hotkeys = []
        self.name = displayName

        self.vlayout = MyVBoxLayout()  #QVBoxLayout()
        self.vlayout.setAlignment(Qt.AlignTop)
        self.vlayout.setSpacing(4)
        self.vlayout.setMargin(4)
        #self.vlayout.setContentsMargins(0, 0, 0, 44)

        if stateChangedSignalSlot is not None:
            self.stateChanged.connect(stateChangedSignalSlot)

        # LOG.info("AttrAreaWidget constructor : property {} ...".format(property))
        self.numGroups = len(property) if property is not None else 0

        self.buttonGroupsDescDict = {}
        self.buttonGroupWidgets = {}

        for i in xrange(self.numGroups):
            thisGroupProperty = property[i]  # thisGroupProperty is a dict
            thisGroupName = thisGroupProperty.get(
                'name')  # thisGroupName is a string
            thisGroupOption = thisGroupProperty.get(
                'option'
            )  # thisGroupOption is a tuple of dicts. each dict describes a checkbox
            thisGroupText = thisGroupProperty.get('displaytext')
            thisGroup
            isExclusive = False if thisGroupName in config.NON_EXCLUSIVE_ATTRS_TAG_LIST else True

            if thisGroupName == config.ANNOTATION_PERSONBIKE_TYPE_GROUP_TOKEN:
                checkboxWidgetMinWidthInPixels = 52
            else:
                checkboxWidgetMinWidthInPixels = 38

            thisGroupWidget = checkBoxListWidget(thisGroupName, thisGroupText,
                                                 isExclusive, thisGroupOption,
                                                 self.stateChanged)
            if thisGroupName == config.ANNOTATION_UPPER_COLOR_TOKEN:
                thisGroupWidget.setMaxCheckedNum(config.MAX_UPPER_COLOR_NUMBER)
                thisGroupWidget.setMaxSeperateWidgetNum(
                    config.GUI_MAX_SEPERATE_UPPER_COLOR_WIDGET_NUMBER)
            elif thisGroupName == config.ANNOTATION_LOWER_COLOR_TOKEN:
                thisGroupWidget.setMaxCheckedNum(config.MAX_LOWER_COLOR_NUMBER)
                thisGroupWidget.setMaxSeperateWidgetNum(
                    config.GUI_MAX_SEPERATE_LOWER_COLOR_WIDGET_NUMBER)
            elif thisGroupName == config.ANNOTATION_VEHICLE_COLOR_TOKEN:
                thisGroupWidget.setMaxCheckedNum(
                    config.MAX_VEHICLE_COLOR_NUMBER)
                thisGroupWidget.setMaxSeperateWidgetNum(
                    config.GUI_MAX_SEPERATE_VEHICLE_COLOR_WIDGET_NUMBER)
            else:
                thisGroupWidget.setMaxCheckedNum(
                    config.MAX_CHECKED_OPTIONS_NUMBER)
                thisGroupWidget.setMaxSeperateWidgetNum(
                    config.MAX_OPTIONS_NUMBER)

            thisGroupCheckboxs = {}
            optCnt = 0
            for checkboxOption in thisGroupOption:

                thisCheckboxName = checkboxOption[
                    config.METADATA_ATTR_VALUE_TOKEN]
                thisCheckboxText = checkboxOption[
                    config.METADATA_DISPLAYTEXT_TOKEN]

                thisCheckboxProperty = checkboxOption.copy()

                # get widget display color
                if thisGroupName == config.ANNOTATION_UPPER_COLOR_TOKEN or thisGroupName == config.ANNOTATION_LOWER_COLOR_TOKEN or thisGroupName == config.ANNOTATION_VEHICLE_COLOR_TOKEN:
                    thisCheckboxBkgColorChecked = thisCheckboxProperty[
                        config.COLOR_ATTR_RGB_VALUE_TOKEN]
                    thisCheckboxBkgColor = thisCheckboxBkgColorChecked
                else:
                    thisCheckboxBkgColorChecked = thisCheckboxProperty[
                        config.METADATA_DISPLAYCOLOR_TOKEN]
                    thisCheckboxBkgColor = None

                # calc widget text color
            # thisCheckboxBkgColorChecked string pattern: #123456
                txtColorChecked = None
                if thisCheckboxBkgColorChecked is not None:
                    import math
                    rgba = utils.hexColorStrToRGBA(thisCheckboxBkgColorChecked)
                    distance = math.sqrt((rgba[0] - 255)**2 +
                                         (rgba[1] - 255)**2 +
                                         (rgba[2] - 255)**2)
                    txtColorChecked = '#ffffff' if distance > config.GUI_COLOR_TAG_TEXT_BLACKWHITE_TOGGLE_THRESHOLD else '#000000'

                txtColor = txtColorChecked if thisGroupName == config.ANNOTATION_UPPER_COLOR_TOKEN or thisGroupName == config.ANNOTATION_LOWER_COLOR_TOKEN or thisGroupName == config.ANNOTATION_VEHICLE_COLOR_TOKEN else None

                thisGroupCheckboxs[
                    thisCheckboxName] = thisCheckboxProperty  # add checkbox record to this checkbox group record

                # add widget to this group
                if optCnt < thisGroupWidget._maxSeperateWidgetNum:
                    thisGroupWidget.add_checkbox(
                        thisCheckboxName, thisCheckboxText,
                        thisCheckboxBkgColor, txtColor,
                        thisCheckboxBkgColorChecked, txtColorChecked,
                        checkboxWidgetMinWidthInPixels)
                else:
                    thisGroupWidget.add_popup_button(
                        config.GUI_MORE_WIDGET_DISPLAYTEXT,
                        thisCheckboxName,
                        thisCheckboxText,
                        popupbutton_background_color="#808080",
                        popupbutton_text_color="#ffffff",
                        checkbox_background_color=thisCheckboxBkgColor,
                        checkbox_text_color=txtColor)

                optCnt += 1

            # thisGroupWidget.add_checkbox("Unset", u"Unset")

            thisGroupWidget.setLayout(thisGroupWidget.flayout)
            self.vlayout.addWidget(thisGroupWidget)

            self.checkboxGroupsDescDict[
                thisGroupName] = thisGroupCheckboxs  # add this checkbox group record to checkbox groups recrod
            self.checkboxGroupWidgets[thisGroupName] = thisGroupWidget

        # LOG.info("checkboxGroupWidgets = {} checkboxGroupsDescDict = {}".format(self.checkboxGroupWidgets, self.checkboxGroupsDescDict)

        self.vlayout.addStretch(1)
        self.setLayout(self.vlayout)
        return

    def stateHasChanged(self, checkedWidgetsAttrDescDict):
        # LOG.info("AttrAreaWidget.stateChanged with {}".format(checkedWidgetsAttrDescDict))
        return

    def get_checked_widgets_name(self, group_name):
        widget = self.checkboxGroupWidgets.get(str(group_name), None)
        # LOG.info("AttrAreaWidget.get_checked_widgets_name ... widget = {}".format(widget))
        checkedWidgetsNameList = widget.get_checked_widgets_name(
        ) if widget is not None else None
        # LOG.info("AttrAreaWidget.get_checked_widgets_name ... checkedWidget = {}".format(checkedWidget))
        return checkedWidgetsNameList

    def add_hotkey(self, choice, name, hotkey):
        self.hotkeys.append((choice, name, hotkey))

    def get_checked_widgets_attrDesc(self):
        descsDict = {}
        for widgetName, widgetInfo in self.checkboxWidgetsInfo.items():
            if widgetInfo[0].isChecked():
                desc = [None, None, None, None]
                desc[
                    checkedWidgetsAttrDescList_checkedWidget_idx] = widgetInfo[
                        0]
                desc[
                    checkedWidgetsAttrDescList_checkedWidgetText_idx] = widgetInfo[
                        1]
                desc[
                    checkedWidgetsAttrDescList_checkedWidgetStyleSheet_idx] = widgetInfo[
                        2]
                descsDict[widgetName] = desc

        for widgetInfo in self.popupWidgetsInfo.values():
            for actionWidget in widgetInfo[
                    popupWidgetsInfo_actionsWidgetList_idx]:
                if actionWidget.isChecked():
                    idx = widgetInfo[
                        popupWidgetsInfo_actionsWidgetList_idx].index(
                            actionWidget)
                    desc = [None, None, None, None]
                    desc[
                        checkedWidgetsAttrDescList_checkedWidget_idx] = actionWidget
                    desc[
                        checkedWidgetsAttrDescList_checkedWidgetText_idx] = widgetInfo[
                            popupWidgetsInfo_actionsTextList_idx][idx]
                    desc[
                        checkedWidgetsAttrDescList_checkedWidgetStyleSheet_idx] = widgetInfo[
                            popupWidgetsInfo_actionsWidgetStyleSheet_idx]
                    desc[
                        checkedWidgetsAttrDescList_checkedWidgetPopupParentWidget_idx] = widgetInfo[
                            popupWidgetsInfo_pushButtonWidget_idx]
                    widgetName = widgetInfo[
                        popupWidgetsInfo_actionsNameList_idx][idx]
                    descsDict[widgetName] = desc

        return descsDict

    def startEditMode(self, model_items):
        return