Example #1
0
    def __iniGraphicsSystem(self):  ##初始化 graphics View系统
        rect = QRectF(-200, -100, 400, 200)
        self.scene = QGraphicsScene(rect)  #scene逻辑坐标系定义
        self.view.setScene(self.scene)

        ## 画一个矩形框,大小等于scene
        item = QGraphicsRectItem(rect)  #矩形框正好等于scene的大小
        item.setFlag(QGraphicsItem.ItemIsSelectable)  #可选,
        item.setFlag(QGraphicsItem.ItemIsFocusable)  #可以有焦点
        pen = QPen()
        pen.setWidth(2)
        item.setPen(pen)
        self.scene.addItem(item)

        ##一个位于scene中心的椭圆,测试局部坐标
        #矩形框内创建椭圆,绘图项的局部坐标,左上角(-100,-50),宽200,高100
        item2 = QGraphicsEllipseItem(-100, -50, 200, 100)
        item2.setPos(0, 0)
        item2.setBrush(QBrush(Qt.blue))
        item2.setFlag(QGraphicsItem.ItemIsSelectable)  #可选,
        item2.setFlag(QGraphicsItem.ItemIsFocusable)  #可以有焦点
        item2.setFlag(QGraphicsItem.ItemIsMovable)  #可移动

        self.scene.addItem(item2)

        ##一个圆,中心位于scene的边缘
        item3 = QGraphicsEllipseItem(-50, -50, 100, 100)  #矩形框内创建椭圆,绘图项的局部坐标
        item3.setPos(rect.right(), rect.bottom())
        item3.setBrush(QBrush(Qt.red))
        item3.setFlag(QGraphicsItem.ItemIsSelectable)  #可选,
        item3.setFlag(QGraphicsItem.ItemIsFocusable)  #可以有焦点
        item3.setFlag(QGraphicsItem.ItemIsMovable)  #可移动
        self.scene.addItem(item3)

        self.scene.clearSelection()
    def __init__(self, part_instance: ObjectInstance, viewroot: GridRootItemT):
        """Summary

        Args:
            part_instance: ``ObjectInstance`` of the ``Part``
            viewroot: ``GridRootItem``
            parent: Default is ``None``
        """
        super(GridNucleicAcidPartItem, self).__init__(part_instance, viewroot)

        self._getActiveTool = viewroot.manager.activeToolGetter
        m_p = self._model_part
        self._controller = NucleicAcidPartItemController(self, m_p)
        self.scale_factor: float = self._RADIUS / m_p.radius()
        self.active_virtual_helix_item: GridVirtualHelixItem = None
        self.prexover_manager = PreXoverManager(self)
        self.hide()  # hide while until after attemptResize() to avoid flicker

        # set this to a token value
        self._rect: QRectF = QRectF(0., 0., 1000., 1000.)
        self.boundRectToModel()
        self.setPen(getNoPen())
        self.setRect(self._rect)

        self.setAcceptHoverEvents(True)

        # Cache of VHs that were active as of last call to activeGridChanged
        # If None, all grids will be redrawn and the cache will be filled.
        # Connect destructor. This is for removing a part from scenes.

        # initialize the NucleicAcidPartItem with an empty set of old coords
        self.setZValue(styles.ZPARTITEM)
        outline = QGraphicsRectItem(self)
        self.outline: QGraphicsRectItem = outline
        o_rect = self._configureOutline(outline)
        outline.setFlag(QGraphicsItem.ItemStacksBehindParent)
        outline.setZValue(styles.ZDESELECTOR)
        model_color = m_p.getColor()
        outline.setPen(getPenObj(model_color, _DEFAULT_WIDTH))

        GC_SIZE = 10
        self.grab_cornerTL: GrabCornerItem = GrabCornerItem(
            GC_SIZE, model_color, True, self)
        self.grab_cornerTL.setTopLeft(o_rect.topLeft())
        self.grab_cornerBR: GrabCornerItem = GrabCornerItem(
            GC_SIZE, model_color, True, self)
        self.grab_cornerBR.setBottomRight(o_rect.bottomRight())
        self.griditem: GridItem = GridItem(self,
                                           self._model_props['grid_type'])
        self.griditem.setZValue(1)
        self.grab_cornerTL.setZValue(2)
        self.grab_cornerBR.setZValue(2)

        # select upon creation
        for part in m_p.document().children():
            if part is m_p:
                part.setSelected(True)
            else:
                part.setSelected(False)
        self.show()
    def __init__(self, *args):
        super(Demo, self).__init__(*args)
  
        loadUi('MainWindow.ui', self)
        scene = QGraphicsScene()

        rectItem = QGraphicsRectItem(QRectF(0, 0, 320, 240))
        rectItem.setBrush(Qt.red)
        #rectItem.setPen(Qt.NoPen)
        rectItem.setFlag(QGraphicsItem.ItemIsMovable)
        scene.addItem(rectItem)

        ellipseItem = QGraphicsEllipseItem(QRectF(0, 0, 200, 200))
        ellipseItem.setBrush(Qt.blue)
        #ellipseItem.setPen(Qt.NoPen)
        ellipseItem.setFlag(QGraphicsItem.ItemIsMovable)
        scene.addItem(ellipseItem)

        rectSizeGripItem = SizeGripItem(SimpleResizer(rectItem), rectItem)
        ellipseSizeGripItem = SizeGripItem(SimpleResizer(ellipseItem), ellipseItem)

        graphicsView = QGraphicsView(self)
        graphicsView.setScene(scene)

        self.setCentralWidget(graphicsView)
Example #4
0
 def drawRect(self):
     rect = QRectF(self.originPos, self.currentPos)
     rect_item = QGraphicsRectItem(rect)
     rect_item.setPen(self.pen)
     rect_item.setFlag(QGraphicsItem.ItemIsMovable, True)
     if len(self.items()) > 0:
         self.clearLastItem()
     self.addItem(rect_item)
Example #5
0
    def display_markers(self):
        """Add markers on top of first plot."""
        for item in self.idx_markers:
            self.scene.removeItem(item)
        self.idx_markers = []

        window_start = self.parent.value('window_start')
        window_length = self.parent.value('window_length')
        window_end = window_start + window_length
        y_distance = self.parent.value('y_distance')

        markers = []
        if self.parent.info.markers is not None:
            if self.parent.value('marker_show'):
                markers = self.parent.info.markers

        for mrk in markers:
            if window_start <= mrk['end'] and window_end >= mrk['start']:

                mrk_start = max((mrk['start'], window_start))
                mrk_end = min((mrk['end'], window_end))
                color = QColor(self.parent.value('marker_color'))

                item = QGraphicsRectItem(mrk_start, 0,
                                         mrk_end - mrk_start,
                                         len(self.idx_label) * y_distance)
                item.setPen(color)
                item.setBrush(color)
                item.setZValue(-9)
                self.scene.addItem(item)

                item = TextItem_with_BG(color.darker(200))
                item.setText(mrk['name'])
                item.setPos(mrk['start'],
                            len(self.idx_label) *
                            self.parent.value('y_distance'))
                item.setFlag(QGraphicsItem.ItemIgnoresTransformations)
                item.setRotation(-90)
                self.scene.addItem(item)
                self.idx_markers.append(item)
Example #6
0
    def display_markers(self):
        """Add markers on top of first plot."""
        for item in self.idx_markers:
            self.scene.removeItem(item)
        self.idx_markers = []

        window_start = self.parent.value('window_start')
        window_length = self.parent.value('window_length')
        window_end = window_start + window_length
        y_distance = self.parent.value('y_distance')

        markers = []
        if self.parent.info.markers is not None:
            if self.parent.value('marker_show'):
                markers = self.parent.info.markers

        for mrk in markers:
            if window_start <= mrk['end'] and window_end >= mrk['start']:

                mrk_start = max((mrk['start'], window_start))
                mrk_end = min((mrk['end'], window_end))
                color = QColor(self.parent.value('marker_color'))

                item = QGraphicsRectItem(mrk_start, 0,
                                         mrk_end - mrk_start,
                                         len(self.idx_label) * y_distance)
                item.setPen(color)
                item.setBrush(color)
                item.setZValue(-9)
                self.scene.addItem(item)

                item = TextItem_with_BG(color.darker(200))
                item.setText(mrk['name'])
                item.setPos(mrk['start'],
                            len(self.idx_label) *
                            self.parent.value('y_distance'))
                item.setFlag(QGraphicsItem.ItemIgnoresTransformations)
                item.setRotation(-90)
                self.scene.addItem(item)
                self.idx_markers.append(item)
Example #7
0
class Tile(QGraphicsRectItem):
    def __init__(self,
                 letter,
                 points,
                 coords,
                 scale,
                 on_position_change=None,
                 move_to_rack=None,
                 parent=None):
        QGraphicsRectItem.__init__(self, MARGIN, MARGIN,
                                   SQUARE_SIZE - 2 * MARGIN,
                                   SQUARE_SIZE - 2 * MARGIN, parent)
        if on_position_change:
            self.on_position_change = on_position_change
        if move_to_rack:
            self.move_to_rack = move_to_rack

        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.points = points
        self.letter = letter

        self.scale = scale
        self.setScale(self.scale)
        self.setZValue(3)

        self.setPen(QPen(YELLOW2, 0))
        self.setBrush(QBrush(YELLOW))

        tile_letter = letter.upper()
        self.letter_item = QGraphicsSimpleTextItem(tile_letter, self)

        self.font = QFont("Verdana", 20)
        if not points:
            self.font.setBold(True)
        font_metrics = QFontMetrics(self.font)
        height = font_metrics.height()
        width = font_metrics.width(tile_letter)

        self.letter_item.setX((SQUARE_SIZE - width) / 2 - MARGIN)
        self.letter_item.setY((SQUARE_SIZE - height) / 2 - MARGIN)
        self.letter_item.setFont(self.font)
        self.letter_item.setBrush(QBrush(SEA_GREEN))

        self.shadow = QGraphicsRectItem(MARGIN * 2, MARGIN * 2, SQUARE_SIZE,
                                        SQUARE_SIZE, self)
        self.shadow.setFlag(QGraphicsItem.ItemStacksBehindParent)
        self.shadow.setBrush(QBrush(TRANSPARENT_BLACK))
        self.shadow.setPen(QPen(TRANSPARENT, 0))
        self.shadow.hide()

        self.setPos(coords.x * SQUARE_SIZE * scale,
                    coords.y * SQUARE_SIZE * scale)
        self.coords = None
        self.update_coords()

        self.old_position = None
        self.old_coords = None

        self.is_placed = False

        if points:
            self.add_points()

    def __str__(self):
        return self.letter

    def add_points(self):
        points = QGraphicsSimpleTextItem(str(self.points), self)
        font = QFont("Verdana", 10)
        font_metrics = QFontMetrics(font)
        height = font_metrics.height()
        width = font_metrics.width(str(self.points))
        points.setFont(font)
        points.setBrush(QBrush(SEA_GREEN))
        points.setX(SQUARE_SIZE - MARGIN - width)
        points.setY(SQUARE_SIZE - MARGIN - height)

    def resize(self, scale):
        self.scale = scale
        self.setScale(scale)
        self.setPos(self.coords.x * SQUARE_SIZE * scale,
                    self.coords.y * SQUARE_SIZE * scale)

    def change_to_blank(self, new_letter):
        if self.letter == BLANK:
            self.letter = new_letter
            self.letter_item.setText(new_letter.upper())
            self.font.setBold(True)
            font_metrics = QFontMetrics(self.font)
            height = font_metrics.height()
            width = font_metrics.width(self.letter)
            self.letter_item.setFont(self.font)
            self.letter_item.setX((SQUARE_SIZE - width) / 2 - MARGIN)
            self.letter_item.setY((SQUARE_SIZE - height) / 2 - MARGIN)

    def change_back(self):
        self.letter = BLANK
        self.letter_item.setText(BLANK)

    def get_letter_and_points(self):
        return self.letter, self.points

    def mousePressEvent(self, event):
        if self.is_placed:
            return
        if event.button() == Qt.RightButton:
            return
        self.setScale(self.scale * 1.1)

        self.setZValue(10)
        self.old_position = self.pos()
        self.old_coords = self.coords

        self.setPos(self.x() - 2 * MARGIN, self.y() - 2 * MARGIN)
        self.shadow.show()
        QGraphicsRectItem.mousePressEvent(self, event)

    def mouseReleaseEvent(self, event):
        if self.is_placed:
            return
        if event.button() == Qt.RightButton:
            self.move_to_rack(self)
            return
        self.setScale(self.scale)

        current_position = self.pos()
        self.setX(
            round((self.x() + MARGIN * 2) / (SQUARE_SIZE * self.scale)) *
            SQUARE_SIZE * self.scale)
        self.setY(
            round((self.y() + MARGIN * 2) / (SQUARE_SIZE * self.scale)) *
            SQUARE_SIZE * self.scale)

        if current_position != self.pos():
            self.update_coords()
            self.on_position_change(self)

        self.setZValue(3)
        self.shadow.hide()
        QGraphicsRectItem.mouseReleaseEvent(self, event)

    def update_coords(self):
        x = round(self.x() / SQUARE_SIZE / self.scale)
        y = round(self.y() / SQUARE_SIZE / self.scale)
        self.coords = Coords(x, y)

    def move(self, position):
        self.setPos(position)
        self.update_coords()

    def move_to_coords(self, coords):
        position = QPoint(coords.x * SQUARE_SIZE * self.scale,
                          coords.y * SQUARE_SIZE * self.scale)
        self.move(position)

    def undo_move(self):
        self.setPos(self.old_position)
        self.update_coords()

    def swap_with_other(self, other):
        other.move(self.old_position)

    def remove_highlight(self):
        self.letter_item.setBrush(QBrush(SEA_GREEN))

    def place(self):
        self.letter_item.setBrush(QBrush(LIGHT_SEA_GREEN))
        self.setBrush(QBrush(YELLOW2))
        self.setPen(QPen(YELLOW2, 0))
        self.setFlag(QGraphicsItem.ItemIsMovable, False)
        self.is_placed = True
Example #8
0
class NodeTemplateItem():
    ''' 
    This represents one node template on the diagram.  A node template can be on many diagrams
    This class creates the rectangle graphics item and the text graphics item and adds them to the scene.
    '''
    def __init__(self, scene, x, y, nodeTemplateDict=None, NZID=None):
        self.scene = scene
        self.logMsg = None
        self.x = x
        self.y = y
        self.nodeTemplateDict = nodeTemplateDict
        #        self.name = self.nodeTemplateDict.get("name", "")   THIS HAS BEEN REPLACED BY THE name FUNCTION - SEE BELOW
        self.diagramType = "Node Template"
        self.displayText = None
        self.model = self.scene.parent.model
        self.gap = 100
        self.relList = []
        # assign a unique key if it doesn't already have one
        if NZID == None:
            self.NZID = str(uuid.uuid4())
        else:
            self.NZID = NZID

        # init graphics objects to none
        self.TNode = None
        self.TNtext = None

        # draw the node template on the diagram
        self.drawIt()

    def name(self, ):
        return self.nodeTemplateDict.get("name", "")

    def getX(self, ):
        return self.TNode.boundingRect().x()

    def getY(self, ):
        return self.TNode.boundingRect().y()

    def getHeight(self, ):
        return self.TNode.boundingRect().height()

    def getWidth(self, ):
        return self.TNode.boundingRect().width()

    def getRelList(self, ):
        '''return a list of all relationitems that are inbound or outbound from this node template.
          do not include self referencing relationships
        '''
        return [
            diagramItem
            for key, diagramItem in self.scene.parent.itemDict.items()
            if diagramItem.diagramType == "Relationship Template" and (
                diagramItem.startNZID == self.NZID
                or diagramItem.endNZID == self.NZID)
        ]

    def getPoint(self, offset=None):
        '''
        This function is used by the template diagram to calculate the location to drop a node template on the diagram
        '''
        if offset is None:
            return QPointF(self.x, self.y)
        else:
            return QPointF(self.x + offset, self.y + offset)

    def getFormat(self, ):
        '''
        determine if the Node Template  has a template format or should use the project default format
        '''
        # get the node Template custom format
        customFormat = self.nodeTemplateDict.get("TNformat", None)

        if not customFormat is None:
            # get the template custom format
            self.nodeFormat = TNodeFormat(formatDict=customFormat)
        else:
            # get the project default format
            self.nodeFormat = TNodeFormat(
                formatDict=self.model.modelData["TNformat"])

    def clearItem(self, ):

        if (not self.TNode is None and not self.TNode.scene() is None):
            self.TNode.scene().removeItem(self.TNode)
        if (not self.TNtext is None and not self.TNtext.scene() is None):
            self.TNtext.scene().removeItem(self.TNtext)

    def drawIt(self, ):

        # get current format as it may have changed
        self.getFormat()

        # create the qgraphicsItems if they don't exist
        if self.TNode is None:
            # create the rectangle
            self.TNode = QGraphicsRectItem(QRectF(
                self.x, self.y, self.nodeFormat.formatDict["nodeWidth"],
                self.nodeFormat.formatDict["nodeHeight"]),
                                           parent=None)
            self.TNode.setZValue(NODELAYER)
            self.TNode.setFlag(QGraphicsItem.ItemIsMovable, True)
            self.TNode.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
            self.TNode.setFlag(QGraphicsItem.ItemIsSelectable, True)
            self.TNode.setSelected(True)
            self.TNode.setData(1, self.NZID)  # get with self.INode.data(1)
            self.TNode.setData(ITEMTYPE, NODETEMPLATE)
            # create the text box
            self.TNtext = QGraphicsTextItem("", parent=None)
            self.TNtext.setPos(self.x, self.y)
            self.TNtext.setFlag(QGraphicsItem.ItemIsMovable, True)
            self.TNtext.setFlag(QGraphicsItem.ItemIsSelectable, False)
            self.TNtext.setData(NODEID, self.NZID)
            self.TNtext.setData(ITEMTYPE, NODETEMPLATETEXT)
            self.TNtext.setZValue(NODELAYER)
            # save the location
            self.x = self.TNode.sceneBoundingRect().x()
            self.y = self.TNode.sceneBoundingRect().y()
            # generate the html and resize the rectangle
            self.formatItem()
            # add the graphics items to the scene
            self.scene.addItem(self.TNode)
            self.scene.addItem(self.TNtext)
        else:
            # generate the html and resize the rectangle
            self.formatItem()

    def formatItem(self, ):

        # configure the formatting aspects of the qgraphics item
        pen = self.nodeFormat.pen()
        brush = self.nodeFormat.brush()
        self.TNode.setBrush(brush)
        self.TNode.setPen(pen)

        # generate the HTML
        genHTML = self.generateHTML()
        self.TNtext.prepareGeometryChange()
        #        print("before html bounding rectangle width:{}".format(self.TNtext.boundingRect().width()))
        #        print("before html text width:{}".format(self.TNtext.textWidth()))
        self.TNtext.setTextWidth(
            -1
        )  # reset the width to unkonwn so it will calculate a new width based on the new html
        self.TNtext.setHtml(genHTML)
        #        print("after html bounding rectangle width:{}".format(self.TNtext.boundingRect().width()))
        #        print("after html text width:{}".format(self.TNtext.textWidth()))

        # make sure minimum width of 120
        if self.TNtext.boundingRect().width() < 120:
            self.TNtext.setTextWidth(120)
        else:
            self.TNtext.setTextWidth(
                self.TNtext.boundingRect().width()
            )  # you have to do a setTextWidth to get the html to render correctly.

        # set the rectangle item to the same size as the formatted html
        self.TNode.prepareGeometryChange()
        currentRect = self.TNode.rect()
        # insure minimum height of 120
        if self.TNtext.boundingRect().height() < 120:
            currentRect.setHeight(120)
        else:
            currentRect.setHeight(self.TNtext.boundingRect().height())
        currentRect.setWidth(self.TNtext.boundingRect().width())
        self.TNode.setRect(currentRect)

    def generateHTML(self, ):
        '''
        Generate the HTML that formats the node template data inside the rectangle
        '''
        # generate the html
        prefix = "<!DOCTYPE html><html><body>"
        #        head = "<head><style>table, th, td {border: 1px solid black; border-collapse: collapse;}</style></head>"
        suffix = "</body></html>"
        #        blankRow = "<tr><td><left>{}</left></td><td><left>{}</left></td><td><left>{}</left></td><td><left>{}</left></td></tr>".format("", "", "", "")

        name = "<center><b>{}</b></center>".format(
            self.nodeTemplateDict.get("name", ""))
        lbls = self.genLblHTML()
        props = self.genPropHTML()
        genHTML = "{}{}<hr>{}<br><hr>{}{}".format(prefix, name, lbls, props,
                                                  suffix)
        #        print("{} html: {}".format(self.name(), genHTML))

        return genHTML

    def genLblHTML(self):
        #        html = '<table width="90%">'
        html = '<table style="width:90%;border:1px solid black;">'
        if len(self.nodeTemplateDict.get("labels", [])) > 0:
            for lbl in self.nodeTemplateDict.get("labels", []):
                if lbl[NODEKEY] == Qt.Checked:
                    nk = "NK"
                else:
                    nk = "&nbsp;&nbsp;"
                if lbl[REQUIRED] == Qt.Checked:
                    rq = "R"
                else:
                    rq = ""

                html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format(
                    nk, lbl[LABEL], "", rq)
            html = html + "</table>"
        else:
            html = '<tr align="left"><td width="15%"><left>{}</left></td><td  width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format(
                "  ", "NO{}LABELS".format("&nbsp;"), "", "")
            html = html + "</table>"

        return html

    def genPropHTML(self):
        #        PROPERTY, DATATYPE, PROPREQ, DEFAULT, EXISTS, UNIQUE, PROPNODEKEY
        html = '<table style="width:90%;border:1px solid black;">'
        if len(self.nodeTemplateDict.get("properties", [])) > 0:
            for prop in self.nodeTemplateDict.get("properties", []):
                if prop[PROPNODEKEY] == Qt.Checked:
                    nk = "NK"
                else:
                    nk = "&nbsp;&nbsp;"
                if prop[PROPREQ] == Qt.Checked:
                    rq = "R"
                else:
                    rq = ""
                if prop[EXISTS] == Qt.Checked:
                    ex = "E"
                else:
                    ex = ""
                if prop[UNIQUE] == Qt.Checked:
                    uq = "U"
                else:
                    uq = ""
                html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format(
                    nk, prop[PROPERTY], rq, ex, uq)
            html = html + "</table>"
        else:
            html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format(
                "&nbsp;&nbsp;", "NO{}PROPERTIES".format("&nbsp;"), "", "", "")
            html = html + "</table>"
        return html

    def moveIt(self, dx, dy):
        '''
        Move the node rectangle and the node textbox to the delta x,y coordinate.
        '''
        #        print("before moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect()))

        self.TNode.moveBy(dx, dy)
        self.x = self.TNode.sceneBoundingRect().x()
        self.y = self.TNode.sceneBoundingRect().y()
        self.TNtext.moveBy(dx, dy)
        #        print("after moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect()))
        # now redraw all the relationships
        self.drawRels()

    def drawRels(self, ):
        '''Redraw all the relationship lines connected to the Node Template Rectangle'''
        # get a list of the relationship items connected to this node template
        self.relList = self.getRelList()

        # assign the correct inbound/outbound side for the rel
        for rel in self.relList:
            if rel.endNodeItem.NZID != rel.startNodeItem.NZID:  # ignore bunny ears
                rel.assignSide()
        # get a set of all the nodes and sides involved
        nodeSet = set()
        for rel in self.relList:
            if rel.endNodeItem.NZID != rel.startNodeItem.NZID:  # ignore bunny ears
                nodeSet.add((rel.endNodeItem, rel.inboundSide))
                nodeSet.add((rel.startNodeItem, rel.outboundSide))

        # tell each node side to assign rel locations
        for nodeSide in nodeSet:
            nodeSide[0].assignPoint(nodeSide[1])

        ############################################

        # now tell them all to redraw
        for rel in self.relList:
            rel.drawIt2()

    def calcOffset(self, index, totRels):
        offset = [-60, -40, -20, 0, 20, 40, 60]
        offsetStart = [3, 2, 2, 1, 1, 0, 0]
        if totRels > 7:
            totRels = 7
        return offset[offsetStart[totRels - 1] + index]

    def assignPoint(self, side):
        # go through all the rels on a side and assign their x,y coord for that side
        self.relList = self.getRelList()
        sideList = [
            rel for rel in self.relList
            if ((rel.startNZID == self.NZID and rel.outboundSide == side) or (
                rel.endNZID == self.NZID and rel.inboundSide == side))
        ]
        totRels = len(sideList)
        if totRels > 0:
            if side == R:
                # calc center of the side
                x = self.x + self.getWidth()
                y = self.y + self.getHeight() / 2
                # sort the rels connected to this side by the y value
                sideList.sort(key=self.getSortY)
                # assign each of them a position on the side starting in the center and working out in both directions
                for index, rel in enumerate(sideList):
                    if rel.startNZID == self.NZID:
                        rel.outboundPoint = QPointF(
                            x, y + (self.calcOffset(index, totRels)))
                    if rel.endNZID == self.NZID:
                        rel.inboundPoint = QPointF(
                            x, y + (self.calcOffset(index, totRels)))
            elif side == L:
                x = self.x
                y = self.y + self.getHeight() / 2
                sideList.sort(key=self.getSortY)
                for index, rel in enumerate(sideList):
                    if rel.startNZID == self.NZID:
                        rel.outboundPoint = QPointF(
                            x, y + (self.calcOffset(index, totRels)))
                    if rel.endNZID == self.NZID:
                        rel.inboundPoint = QPointF(
                            x, y + (self.calcOffset(index, totRels)))
            elif side == TOP:
                x = self.x + self.getWidth() / 2
                y = self.y
                sideList.sort(key=self.getSortX)
                for index, rel in enumerate(sideList):
                    if rel.startNZID == self.NZID:
                        rel.outboundPoint = QPointF(
                            x + (self.calcOffset(index, totRels)), y)
                    if rel.endNZID == self.NZID:
                        rel.inboundPoint = QPointF(
                            x + (self.calcOffset(index, totRels)), y)
            elif side == BOTTOM:
                x = self.x + self.getWidth() / 2
                y = self.y + self.getHeight()
                sideList.sort(key=self.getSortX)
                for index, rel in enumerate(sideList):
                    if rel.startNZID == self.NZID:
                        rel.outboundPoint = QPointF(
                            x + (self.calcOffset(index, totRels)), y)
                    if rel.endNZID == self.NZID:
                        rel.inboundPoint = QPointF(
                            x + (self.calcOffset(index, totRels)), y)
            else:
                print("error, no side")

    def getSortY(self, rel):
        # if this node is the start node then return the end node's Y
        if rel.startNZID == self.NZID:
            return rel.endNodeItem.TNode.sceneBoundingRect().center().y()
        # if this node is the end node then return the start node's Y
        if rel.endNZID == self.NZID:
            return rel.startNodeItem.TNode.sceneBoundingRect().center().y()
        # this should never happen
        return 0

    def getSortX(self, rel):
        # if this node is the start node then return the end node's X
        if rel.startNZID == self.NZID:
            return rel.endNodeItem.TNode.sceneBoundingRect().center().x()
        # if this node is the end node then return the start node's X
        if rel.endNZID == self.NZID:
            return rel.startNodeItem.TNode.sceneBoundingRect().center().x()
        # this should never happen
        return 0

    def getObjectDict(self, ):
        '''
        This function returns a dictionary with all the data that represents this node template item.  
        The dictionary is added to the Instance Diagram dictionary.'''
        objectDict = {}
        objectDict["NZID"] = self.NZID
        objectDict["name"] = self.nodeTemplateDict.get("name", "")
        objectDict["displayText"] = self.displayText
        objectDict["x"] = self.TNode.sceneBoundingRect().x()
        objectDict["y"] = self.TNode.sceneBoundingRect().y()
        objectDict["diagramType"] = self.diagramType
        objectDict["labels"] = self.nodeTemplateDict.get("labels", [])
        objectDict["properties"] = self.nodeTemplateDict.get("properties", [])

        return objectDict

    def setLogMethod(self, logMethod=None):
        if logMethod is None:
            if self.logMsg is None:
                self.logMsg = self.noLog
        else:
            self.logMsg = logMethod

    def noLog(self, msg):
        return
Example #9
0
class DrawItem():
    '''
    Piirtää kuviot
    '''
    def __init__(self, piirtoalusta):
        self.scene = piirtoalusta.scene
        self.undoStack = piirtoalusta.undoStack
        self.reset()
        self.start = QPoint()
        self.end = QPoint()
        self.pos = QPoint()
        self.color = Qt.black
        self.font = QFont()

    def setRect(self):
        '''
        Määrittää nelikulmion paikkatietojen perusteella
        '''
        rect = QRectF()
        rect.setTopLeft(self.start)
        rect.setBottomRight(self.end)
        return rect

    def drawRect(self):
        '''
        Piirtää esineen ja lisää sen sceneen. Palauttaa esineen Load ja
        Undo luokkia varten.
        '''
        path = QPainterPath()
        rect = self.setRect()
        path.addRect(rect)
        path.simplified()
        #self.rectitem = QGraphicsPathItem(path)
        self.rectitem = PathItem(path, self.undoStack)
        self.rectitem.setPen(QPen(self.color, 2))
        self.rectitem.setFlag(QGraphicsItem.ItemIsSelectable)
        self.rectitem.setFlag(QGraphicsItem.ItemIsMovable)
        self.scene.addItem(self.rectitem)
        return self.rectitem
        '''
        rect = self.setRect()
        self.rectitem = QGraphicsRectItem(rect)
        self.rectitem.setPen(QPen(self.color, 2))
        self.scene.addItem(self.rectitem)
        '''

    def drawEllipse(self):
        rect = self.setRect()
        #self.ellipseitem = QGraphicsEllipseItem(rect)
        self.ellipseitem = EllipseItem(rect, self.undoStack)
        self.ellipseitem.setPen(QPen(self.color, 2))
        self.ellipseitem.setFlag(QGraphicsItem.ItemIsSelectable)
        self.ellipseitem.setFlag(QGraphicsItem.ItemIsMovable)
        self.scene.addItem(self.ellipseitem)
        return self.ellipseitem

    def drawCircle(self):
        erotus = abs(self.end.x() - self.start.x())
        if self.start.y() > self.end.y():
            erotus = -erotus
        self.end.setY(self.start.y() + erotus)
        self.drawEllipse()

    def drawLine(self):
        path = QPainterPath()
        shape = QRectF(0, 0, 1, 1)
        shape.moveCenter(self.pos)
        path.addEllipse(shape)
        self.paths.connectPath(path)
        self.paths.simplified()
        #self.lineitem = QGraphicsPathItem(self.paths)
        self.lineitem = PathItem(self.paths, self.undoStack)
        self.lineitem.setPen(QPen(self.color, 2))
        self.lineitem.setFlag(QGraphicsItem.ItemIsSelectable)
        self.lineitem.setFlag(QGraphicsItem.ItemIsMovable)
        self.scene.addItem(self.lineitem)
        return self.lineitem

    def drawText(self):
        #textitem = QGraphicsTextItem('Text')
        textitem = TextItem('Text', self.undoStack)
        textitem.setPos(self.end)
        textitem.setAcceptHoverEvents(False)
        textitem.setTextInteractionFlags(Qt.TextSelectableByKeyboard
                                         | Qt.TextEditable)
        textitem.setFont(self.font)
        textitem.setDefaultTextColor(self.color)
        textitem.setFlag(QGraphicsItem.ItemIsMovable)
        textitem.setFlag(QGraphicsItem.ItemIsSelectable)
        self.scene.addItem(textitem)
        return textitem

    def reset(self):
        self.ellipseitem = QGraphicsEllipseItem()
        self.rectitem = QGraphicsRectItem()
        self.lineitem = QGraphicsPathItem()
        self.paths = QPainterPath()

    def change_selected_color(self, items):
        '''
        Muttaa listan 'items' esineiden värin arvoon 'self.color'.
        '''
        for item in items:
            if item.type() == 10:  # group
                self.change_selected_color(
                    item.childItems())  # kutsuu itseään joukon jäsenille
            elif item.type() == 8:  # tekstiä
                item.setDefaultTextColor(self.color)
            else:
                item.setPen(QPen(self.color, 2))
Example #10
0
class ImageScene2D(QGraphicsScene):
    """
    The 2D scene description of a tiled image generated by evaluating
    an overlay stack, together with a 2D cursor.
    """

    axesChanged = pyqtSignal(int, bool)
    dirtyChanged = pyqtSignal()

    @property
    def is_swapped(self):
        """
        Indicates whether the dimensions are swapped
        swapping the axis will swap the dimensions and rotating the roi will swap the dimensions
        :return: bool
        """
        return bool(self._swapped) != bool(self._rotation % 2)  # xor

    @property
    def stackedImageSources(self):
        return self._stackedImageSources

    @stackedImageSources.setter
    def stackedImageSources(self, s):
        self._stackedImageSources = s

    @property
    def showTileOutlines(self):
        return self._showTileOutlines

    @showTileOutlines.setter
    def showTileOutlines(self, show):
        self._showTileOutlines = show
        self.invalidate()

    @property
    def showTileProgress(self):
        return self._showTileProgress

    @showTileProgress.setter
    def showTileProgress(self, show):
        self._showTileProgress = show
        self._dirtyIndicator.setVisible(show)

    def resetAxes(self, finish=True):
        # rotation is in range(4) and indicates in which corner of the
        # view the origin lies. 0 = top left, 1 = top right, etc.
        self._rotation = 0
        self._swapped = self._swappedDefault  # whether axes are swapped
        self._newAxes()
        self._setSceneRect()
        self.scene2data, isInvertible = self.data2scene.inverted()
        assert isInvertible
        if finish:
            self._finishViewMatrixChange()

    def _newAxes(self):
        """Given self._rotation and self._swapped, calculates and sets
        the appropriate data2scene transformation.

        """
        # TODO: this function works, but it is not elegant. There must
        # be a simpler way to calculate the appropriate transformation.

        w, h = self.dataShape
        assert self._rotation in range(0, 4)

        # unlike self._rotation, the local variable 'rotation'
        # indicates how many times to rotate clockwise after swapping
        # axes.

        # t1 : do axis swap
        t1 = QTransform()
        if self._swapped:
            t1 = QTransform(0, 1, 0, 1, 0, 0, 0, 0, 1)
            h, w = w, h

        # t2 : do rotation
        t2 = QTransform()
        t2.rotate(self._rotation * 90)

        # t3: shift to re-center
        rot2trans = {0: (0, 0), 1: (h, 0), 2: (w, h), 3: (0, w)}

        trans = rot2trans[self._rotation]
        t3 = QTransform.fromTranslate(*trans)

        self.data2scene = t1 * t2 * t3
        if self._tileProvider:
            self._tileProvider.axesSwapped = self._swapped
        self.axesChanged.emit(self._rotation, self._swapped)

    def rot90(self, direction):
        """ direction: left ==> -1, right ==> +1"""
        assert direction in [-1, 1]
        self._rotation = (self._rotation + direction) % 4
        self._newAxes()

    def swapAxes(self, transform):
        self._swapped = not self._swapped
        self._newAxes()

    def _onRotateLeft(self):
        self.rot90(-1)
        self._finishViewMatrixChange()

    def _onRotateRight(self):
        self.rot90(1)
        self._finishViewMatrixChange()

    def _onSwapAxes(self):
        self.swapAxes(self.data2scene)
        self._finishViewMatrixChange()

    def _finishViewMatrixChange(self):
        self.scene2data, isInvertible = self.data2scene.inverted()
        self._setSceneRect()
        self._tiling.data2scene = self.data2scene
        self._tileProvider._onSizeChanged()
        QGraphicsScene.invalidate(self, self.sceneRect())

    @property
    def sceneShape(self):
        return (self.sceneRect().width(), self.sceneRect().height())

    def _setSceneRect(self):
        w, h = self.dataShape
        rect = self.data2scene.mapRect(QRect(0, 0, w, h))
        sw, sh = rect.width(), rect.height()
        self.setSceneRect(0, 0, sw, sh)

        if self._dataRectItem is not None:
            self.removeItem(self._dataRectItem)

        # this property represent a parent to QGraphicsItems which should
        # be clipped to the data, such as temporary capped lines for brushing.
        # This works around ilastik issue #516.
        self._dataRectItem = QGraphicsRectItem(0, 0, sw, sh)
        self._dataRectItem.setPen(QPen(QColor(0, 0, 0, 0)))
        self._dataRectItem.setFlag(QGraphicsItem.ItemClipsChildrenToShape)
        self.addItem(self._dataRectItem)

    @property
    def dataRectItem(self):
        return self._dataRectItem

    @property
    def dataShape(self):
        """
        The shape of the scene in QGraphicsView's coordinate system.
        """
        return self._dataShape

    @dataShape.setter
    def dataShape(self, value):
        """
        Set the size of the scene in QGraphicsView's coordinate system.
        dataShape -- (widthX, widthY),
        where the origin of the coordinate system is in the upper left corner
        of the screen and 'x' points right and 'y' points down
        """
        assert len(value) == 2
        self._dataShape = value
        self.reset()
        self._finishViewMatrixChange()

    def setCacheSize(self, cache_size):
        self._tileProvider.set_cache_size(cache_size)

    def cacheSize(self):
        return self._tileProvider.cache_size

    def setTileWidth(self, tileWidth):
        self._tileWidth = tileWidth
        PreferencesManager().set("ImageScene2D", "tileWidth", tileWidth)

    def tileWidth(self):
        return self._tileWidth

    def setPrefetchingEnabled(self, enable):
        self._prefetching_enabled = enable

    def setPreemptiveFetchNumber(self, n):
        if n > self.cacheSize() - 1:
            self._n_preemptive = self.cacheSize() - 1
        else:
            self._n_preemptive = n

    def preemptiveFetchNumber(self):
        return self._n_preemptive

    def invalidateViewports(self, sceneRectF):
        """Call invalidate on the intersection of all observing viewport-rects and rectF."""
        sceneRectF = sceneRectF if sceneRectF.isValid() else self.sceneRect()
        for view in self.views():
            QGraphicsScene.invalidate(self, sceneRectF.intersected(view.viewportRect()))

    def reset(self):
        """Reset rotations, tiling, etc. Called when first initialized
        and when the underlying data changes.

        """
        self.resetAxes(finish=False)

        self._tiling = Tiling(self._dataShape, self.data2scene, name=self.name, blockSize=self.tileWidth())

        self._tileProvider = TileProvider(self._tiling, self._stackedImageSources)
        self._tileProvider.sceneRectChanged.connect(self.invalidateViewports)

        if self._dirtyIndicator:
            self.removeItem(self._dirtyIndicator)
        del self._dirtyIndicator
        self._dirtyIndicator = DirtyIndicator(self._tiling)
        self.addItem(self._dirtyIndicator)
        self._dirtyIndicator.setVisible(False)

    def mouseMoveEvent(self, event):
        """
        Normally our base class (QGraphicsScene) distributes mouse events to the
        various QGraphicsItems in the scene. But when the mouse is being dragged,
        it only sends events to the one object that was under the mouse when the
        button was first pressed.

        Here, we forward all events to QGraphicsItems on the drag path, even if
        they're just brushed by the mouse incidentally.
        """
        super(ImageScene2D, self).mouseMoveEvent(event)

        if not event.isAccepted() and event.buttons() != Qt.NoButton:
            if self.last_drag_pos is None:
                self.last_drag_pos = event.scenePos()

            # As a special feature, find the item and send it this event.
            path = QPainterPath(self.last_drag_pos)
            path.lineTo(event.scenePos())
            items = self.items(path)
            for item in items:
                item.mouseMoveEvent(event)
            self.last_drag_pos = event.scenePos()
        else:
            self.last_drag_pos = None

    def mousePressEvent(self, event):
        """
        By default, our base class (QGraphicsScene) only sends mouse press events to the top-most item under the mouse.
        When labeling edges, we want the edge label layer to accept mouse events, even if it isn't on top.
        Therefore, we send events to all items under the mouse, until the event is accepted.
        """
        super(ImageScene2D, self).mousePressEvent(event)
        if not event.isAccepted():
            items = self.items(event.scenePos())
            for item in items:
                item.mousePressEvent(event)
                if event.isAccepted():
                    break

    def __init__(
        self, posModel, along, preemptive_fetch_number=5, parent=None, name="Unnamed Scene", swapped_default=False
    ):
        """
        * preemptive_fetch_number -- number of prefetched slices; 0 turns the feature off
        * swapped_default -- whether axes should be swapped by default.

        """
        QGraphicsScene.__init__(self, parent=parent)

        self._along = along
        self._posModel = posModel

        # QGraphicsItems can change this if they are in a state that should temporarily forbid brushing
        # (For example, when the slice intersection marker is in 'draggable' state.)
        self.allow_brushing = True

        self._dataShape = (0, 0)
        self._dataRectItem = None  # A QGraphicsRectItem (or None)
        self._offsetX = 0
        self._offsetY = 0
        self.name = name
        self._tileWidth = PreferencesManager().get("ImageScene2D", "tileWidth", default=512)

        self._stackedImageSources = StackedImageSources(LayerStackModel())
        self._showTileOutlines = False

        # FIXME: We don't show the red 'progress pies' because they look terrible.
        #        If we could fix their timing, maybe it would be worth it.
        self._showTileProgress = False

        self._tileProvider = None
        self._dirtyIndicator = None
        self._prefetching_enabled = False

        self._swappedDefault = swapped_default
        self.reset()

        # BowWave preemptive caching
        self.setPreemptiveFetchNumber(preemptive_fetch_number)
        self._course = (1, 1)  # (along, pos or neg direction)
        self._time = self._posModel.time
        self._channel = self._posModel.channel
        self._posModel.timeChanged.connect(self._onTimeChanged)
        self._posModel.channelChanged.connect(self._onChannelChanged)
        self._posModel.slicingPositionChanged.connect(self._onSlicingPositionChanged)

        self._allTilesCompleteEvent = threading.Event()
        self.dirty = False

        # We manually keep track of the tile-wise QGraphicsItems that
        # we've added to the scene in this dict, otherwise we would need
        # to use O(N) lookups for every tile by calling QGraphicsScene.items()
        self.tile_graphicsitems = defaultdict(set)  # [Tile.id] -> set(QGraphicsItems)

        self.last_drag_pos = None  # See mouseMoveEvent()

    def drawForeground(self, painter, rect):
        if self._tiling is None:
            return

        if self._showTileOutlines:
            tile_nos = self._tiling.intersected(rect)

            for tileId in tile_nos:
                ## draw tile outlines
                # Dashed black line
                pen = QPen()
                pen.setWidth(0)
                pen.setDashPattern([5, 5])
                painter.setPen(pen)
                painter.drawRect(self._tiling.imageRects[tileId])

                # Dashed white line
                # (offset to occupy the spaces in the dashed black line)
                pen = QPen()
                pen.setWidth(0)
                pen.setDashPattern([5, 5])
                pen.setDashOffset(5)
                pen.setColor(QColor(Qt.white))
                painter.setPen(pen)
                painter.drawRect(self._tiling.imageRects[tileId])

    def indicateSlicingPositionSettled(self, settled):
        if self._showTileProgress:
            self._dirtyIndicator.setVisible(settled)

    def drawBackground(self, painter, sceneRectF):
        if self._tileProvider is None:
            return

        # FIXME: For some strange reason, drawBackground is called with
        #        a much larger sceneRectF than necessasry sometimes.
        #        This can happen after panSlicingViews(), for instance.
        #        Somehow, the QGraphicsScene gets confused about how much area
        #        it needs to draw immediately after the ImageView's scrollbar is panned.
        #        As a workaround, we manually check the amount of the scene that needs to be drawn,
        #        instead of relying on the above sceneRectF parameter to be correct.
        if self.views():
            sceneRectF = self.views()[0].viewportRect().intersected(sceneRectF)

        if not sceneRectF.isValid():
            return

        tiles = self._tileProvider.getTiles(sceneRectF)
        allComplete = True
        for tile in tiles:
            # We always draw the tile, even though it might not be up-to-date
            # In ilastik's live mode, the user sees the old result while adding
            # new brush strokes on top
            # See also ilastik issue #132 and tests/lazy_test.py
            if tile.qimg is not None:
                painter.drawImage(tile.rectF, tile.qimg)

            # The tile also contains a list of any QGraphicsItems that were produced by the layers.
            # If there are any new ones, add them to the scene.
            new_items = set(tile.qgraphicsitems) - self.tile_graphicsitems[tile.id]
            obsolete_items = self.tile_graphicsitems[tile.id] - set(tile.qgraphicsitems)
            for g_item in obsolete_items:
                self.tile_graphicsitems[tile.id].remove(g_item)
                self.removeItem(g_item)
            for g_item in new_items:
                self.tile_graphicsitems[tile.id].add(g_item)
                self.addItem(g_item)

            if tile.progress < 1.0:
                allComplete = False
            if self._showTileProgress:
                self._dirtyIndicator.setTileProgress(tile.id, tile.progress)

        if allComplete:
            if self.dirty:
                self.dirty = False
                self.dirtyChanged.emit()
            self._allTilesCompleteEvent.set()
        else:
            if not self.dirty:
                self.dirty = True
                self.dirtyChanged.emit()
            self._allTilesCompleteEvent.clear()

        # preemptive fetching
        if self._prefetching_enabled:
            upcoming_through_slices = self._bowWave(self._n_preemptive)
            for through in upcoming_through_slices:
                self._tileProvider.prefetch(sceneRectF, through, layer_indexes=None)

    def triggerPrefetch(self, layer_indexes, time_range="current", spatial_axis_range="current", sceneRectF=None):
        """
        Trigger a one-time prefetch for the given set of layers.

        TODO: I'm not 100% sure what happens here for layers with multiple channels.

        layer_indexes: list-of-ints, or None, which means 'all visible'.
        time_range: (start_time, stop_time)
        spatial_axis_range: (start_slice, stop_slice), meaning Z/Y/X depending on our projection (self.along)
        sceneRectF: Used to determine which tiles to request.
                    An invalid QRectF results in all tiles getting refreshed (visible or not).
        """
        # Process parameters
        sceneRectF = sceneRectF or QRectF()

        if time_range == "current":
            time_range = (self._posModel.slicingPos5D[0], self._posModel.slicingPos5D[0] + 1)
        elif time_range == "all":
            time_range = (0, self._posModel.shape5D[0])
        else:
            assert len(time_range) == 2
            assert time_range[0] >= 0 and time_range[1] < self._posModel.shape5D[0]

        spatial_axis = self._along[1]
        if spatial_axis_range == "current":
            spatial_axis_range = (
                self._posModel.slicingPos5D[spatial_axis],
                self._posModel.slicingPos5D[spatial_axis] + 1,
            )
        elif spatial_axis_range == "all":
            spatial_axis_range = (0, self._posModel.shape5D[spatial_axis])
        else:
            assert len(spatial_axis_range) == 2
            assert 0 <= spatial_axis_range[0] < self._posModel.shape5D[spatial_axis]
            assert 0 < spatial_axis_range[1] <= self._posModel.shape5D[spatial_axis]

        # Construct list of 'through' coordinates
        through_list = []
        for t in range(*time_range):
            for s in range(*spatial_axis_range):
                through_list.append((t, s))

        # Make sure the tile cache is big enough to hold the prefetched data.
        if self._tileProvider.cache_size < len(through_list):
            self._tileProvider.set_cache_size(len(through_list))

        # Trigger prefetches
        for through in through_list:
            self._tileProvider.prefetch(sceneRectF, through, layer_indexes)

    def joinRenderingAllTiles(self, viewport_only=True, rect=None):
        """
        Wait until all tiles in the scene have been 100% rendered.
        If sceneRectF is None, use the viewport rect.
        If sceneRectF is an invalid QRectF(), then wait for all tiles.
        Note: If called from the GUI thread, the GUI thread will block until all tiles are rendered!
        """
        # If this is the main thread, keep repainting (otherwise we'll deadlock).
        if threading.current_thread().name == "MainThread":
            if viewport_only:
                sceneRectF = self.views()[0].viewportRect()
            else:
                if rect is None or not isinstance(rect, QRectF):
                    sceneRectF = QRectF()  # invalid QRectF means 'get all tiles'
                else:
                    sceneRectF = rect
            self._tileProvider.waitForTiles(sceneRectF)
        else:
            self._allTilesCompleteEvent.wait()

    def _bowWave(self, n):
        through = [self._posModel.slicingPos5D[axis] for axis in self._along[:-1]]
        t_max = [self._posModel.shape5D[axis] for axis in self._along[:-1]]

        BowWave = []

        a = self._course[0]
        for d in range(1, n + 1):
            m = through[a] + d * self._course[1]
            if m < t_max[a] and m >= 0:
                t = list(through)
                t[a] = m
                BowWave.append(tuple(t))
        return BowWave

    def _onSlicingPositionChanged(self, new, old):
        if (new[self._along[1] - 1] - old[self._along[1] - 1]) < 0:
            self._course = (1, -1)
        else:
            self._course = (1, 1)

    def _onChannelChanged(self, new):
        if (new - self._channel) < 0:
            self._course = (2, -1)
        else:
            self._course = (2, 1)
        self._channel = new

    def _onTimeChanged(self, new):
        if (new - self._time) < 0:
            self._course = (0, -1)
        else:
            self._course = (0, 1)
        self._time = new
Example #11
0
class RectZoomMoveView(QChartView):
    """
    Filter data to be displayed in rectangular body
    """

    rangeSig = pyqtSignal(list)

    def __init__(self, parent=None):
        super(RectZoomMoveView, self).__init__(parent)
        self.setChart(QChart())
        self.chart().setMargins(QMargins(5, 5, 5, 5))
        self.chart().setContentsMargins(-10, -10, -10, -10)
        self.chart().setTitle(" ")
        self.relationState = True

        # Define two rectangles for background and drawing respectively
        self.parentRect = QGraphicsRectItem(self.chart())

        self.parentRect.setFlag(QGraphicsItem.ItemClipsChildrenToShape, True)
        self.parentRect.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.RangeItem = RectRangeItem(parent=self.parentRect)
        self.RangeItem.setZValue(998)
        pen = QPen(Qt.gray)
        pen.setWidth(1)
        self.parentRect.setPen(pen)
        self.parentRect.setZValue(997)

        self.scene().addItem(self.parentRect)
        self.scene().addItem(self.RangeItem)

        self.RangeItem.hide()
        self.m_chartRectF = QRectF()
        self.m_rubberBandOrigin = QPointF(0, 0)
        self.dataLength = 0

        self.RangeItem.selectedChange.connect(self.changeFromRectItem)

        self.BtnsWidget = ViewButtonsWidget(self)
        self.BtnsWidget.refreshBtn.clicked.connect(self.updateView)
        self.BtnsWidget.RelationSig.connect(self.setRelationState)
        self.BtnsWidget.dateRangeEdit.dateRangeSig.connect(self.changDateRect)

    def changDateRect(self, daterange):
        if self.chartTypes == "Bar":
            v = 3
        else:
            v = 2
        l = len(self.RangeItem.rangePoints)
        if l > 2:
            try:
                num = self.mintimeData.date().daysTo(daterange[0])
                left = self.RangeItem.rangePoints[num]
                num = self.mintimeData.date().daysTo(daterange[1])
                right = self.RangeItem.rangePoints[num]
                rect = self.chart().plotArea()
                rect.setLeft(left)
                rect.setRight(right)
            except:
                rect = self.chart().plotArea()
            self.RangeItem.setRect(rect)
            self.RangeItem.updateHandlesPos()
        else:
            try:
                num = self.mintimeData.date().daysTo(daterange[0])
                left = self.RangeItem.rangePoints[num]
                num = self.mintimeData.date().daysTo(daterange[0])
                right = self.RangeItem.rangePoints[num]
                rect = self.chart().plotArea()
                rect.setLeft(left)
                rect.setRight(right)
            except:
                rect = self.chart().plotArea()
            self.RangeItem.setRect(rect)
            self.RangeItem.updateHandlesPos()

    def lineSpace(self, start, end, num):
        res = []
        if self.chartTypes == "Bar":
            step = (end - start) / (num)
            for i in range(num + 2):
                res.append(start + i * step)
        else:
            step = (end - start) / (num - 1)
            for i in range(num + 1):
                res.append(start + i * step)
        return res

    def getRangePoints(self):
        count = self.zoomSeries.count()
        rect = self.chart().plotArea()
        left = rect.left()
        right = rect.right()
        if count == 0:
            self.RangeItem.rangePoints = [left, right]
        else:
            # Get coordinate position for each node
            self.RangeItem.rangePoints = self.lineSpace(left, right, count)

    def setRangeColor(self, color):
        self.RangeItem.setRangeColor(color)

    def setRelationState(self, state):
        self.relationState = state

    def initSeries(self, chartTypes="Bar"):
        self.chartTypes = chartTypes
        axisX = QDateTimeAxis()
        axisX.setFormat("yyyy-MM-dd")
        self.zoomSeries = QLineSeries(self.chart())
        self.chart().addSeries(self.zoomSeries)
        self.chart().setAxisY(QValueAxis(), self.zoomSeries)
        self.chart().setAxisX(axisX, self.zoomSeries)
        self.initView()

    def clearAll(self):
        # Clear all series and axes
        self.chart().removeAllSeries()
        axess = self.chart().axes()
        for axes in axess:
            self.chart().removeAxis(axes)

    def setData(self, timeData, valueData, chartTypes="Bar"):
        axisX = QDateTimeAxis()
        axisX.setFormat("yyyy-MM-dd")

        if self.chartTypes == "Bar":
            # Clear all series
            self.clearAll()
            self.zoomSeries = QLineSeries(self.chart())
            barSeries = QBarSeries(self.chart())
            barset = QBarSet("data")
            barSeries.setBarWidth(0.8)
            barSeries.append(barset)
            for td, vd in zip(timeData, valueData):
                self.zoomSeries.append(td.toMSecsSinceEpoch(), vd)
            barset.append(valueData)
            self.zoomSeries.hide()
            self.chart().addSeries(self.zoomSeries)
            self.chart().addSeries(barSeries)
            self.chart().setAxisY(QValueAxis(), self.zoomSeries)
            axisX.setRange(min(timeData), max(timeData))
            self.chart().setAxisX(axisX, self.zoomSeries)
        elif self.chartTypes == "Scatter":
            # Clear all series
            self.clearAll()
            self.zoomSeries = QLineSeries(self.chart())
            scattSeries = QScatterSeries(self.chart())
            scattSeries.setMarkerSize(8)

            for td, vd in zip(timeData, valueData):
                self.zoomSeries.append(td.toMSecsSinceEpoch(), vd)
                scattSeries.append(td.toMSecsSinceEpoch(), vd)
            self.zoomSeries.hide()
            self.chart().addSeries(self.zoomSeries)
            self.chart().addSeries(scattSeries)
            self.chart().setAxisY(QValueAxis(), self.zoomSeries)
            axisX.setRange(min(timeData), max(timeData))
            self.chart().setAxisX(axisX, self.zoomSeries)
        elif self.chartTypes in ["Line", "PLine"]:
            self.clearAll()
            if self.chartTypes == "Line":
                self.zoomSeries = QLineSeries(self.chart())
            else:
                self.zoomSeries = QSplineSeries(self.chart())
            for td, vd in zip(timeData, valueData):
                self.zoomSeries.append(td.toMSecsSinceEpoch(), vd)
            self.chart().addSeries(self.zoomSeries)

            self.chart().setAxisY(QValueAxis(), self.zoomSeries)
            axisX.setRange(min(timeData), max(timeData))
            self.chart().setAxisX(axisX, self.zoomSeries)
        elif self.chartTypes == "Area":

            self.clearAll()
            self.zoomSeries = QLineSeries()
            self.zoomSeries.setColor(QColor("#666666"))
            for td, vd in zip(timeData, valueData):
                self.zoomSeries.append(td.toMSecsSinceEpoch(), vd)

            areaSeries = QAreaSeries(self.zoomSeries, None)

            self.chart().addSeries(self.zoomSeries)
            self.chart().addSeries(areaSeries)
            self.chart().setAxisY(QValueAxis(), areaSeries)
            axisX.setRange(min(timeData), max(timeData))
            self.chart().setAxisX(axisX, areaSeries)
            self.zoomSeries.hide()
        self.mintimeData = min(timeData)
        self.maxtimeData = max(timeData)
        self.BtnsWidget.dateRangeEdit.setDateRange([
            self.mintimeData.toString("yyyy-MM-dd"),
            self.maxtimeData.toString("yyyy-MM-dd"),
        ])
        self.updateView()

    def resetView(self):

        rect = self.chart().plotArea()
        self.parentRect.setRect(rect)
        topRight = self.chart().plotArea().topRight()
        x = int(topRight.x())
        y = int(topRight.y())
        self.BtnsWidget.setGeometry(QRect(x - 420, 0, 420, 23))
        self.RangeItem.setRect(rect)
        self.RangeItem.show()
        self.save_current_rubber_band()
        self.RangeItem.updateHandlesPos()
        self.apply_nice_numbers()
        self.getRangePoints()
        self.sendRang()

    def initView(self):
        self.RangeItem.hide()
        # Hide y-axis
        if self.chart().axisY():
            self.chart().axisY().setVisible(False)
        if self.chart().axisX():
            self.chart().axisX().setGridLineVisible(False)
        self.m_chartRectF = QRectF()
        self.m_rubberBandOrigin = QPointF(0, 0)
        self.getRangePoints()

    def updateView(self):
        self.RangeItem.hide()
        # Hide y-axis
        if self.chart().axisY():
            self.chart().axisY().setVisible(False)
        if self.chart().axisX():
            self.chart().axisX().setGridLineVisible(False)
        self.m_chartRectF = QRectF()
        self.m_rubberBandOrigin = QPointF(0, 0)
        self.resetView()

    # Map points to chart
    def point_to_chart(self, pnt):
        scene_point = self.mapToScene(pnt)
        chart_point = self.chart().mapToValue(scene_point)
        return chart_point

    # Map chart to points
    def chart_to_view_point(self, char_coord):
        scene_point = self.chart().mapToPosition(char_coord)
        view_point = self.mapFromScene(scene_point)
        return view_point

    # Save positions of rectangles
    def save_current_rubber_band(self):
        rect = self.RangeItem.rect()

        chart_top_left = self.point_to_chart(rect.topLeft().toPoint())
        self.m_chartRectF.setTopLeft(chart_top_left)

        chart_bottom_right = self.point_to_chart(rect.bottomRight().toPoint())
        self.m_chartRectF.setBottomRight(chart_bottom_right)

    # Respond to change in positions of rectangles
    def changeFromRectItem(self, rectIndex):
        self.save_current_rubber_band()
        self.sendRang(rectIndex)

    def sendRang(self, rectIndex=[]):

        if self.RangeItem.rect() != self.parentRect.rect():
            self.BtnsWidget.setPalActive()
        else:
            self.BtnsWidget.setPalDisActive()
        if self.chartTypes == "Bar":
            v = 3
        else:
            v = 2
        if rectIndex == []:
            maxData = QDateTime.fromMSecsSinceEpoch(
                self.zoomSeries.at(len(self.RangeItem.rangePoints) - v).x())
            minData = QDateTime.fromMSecsSinceEpoch(self.zoomSeries.at(0).x())
        else:
            minData = max(rectIndex[0], 0)
            maxData = min(rectIndex[1], len(self.RangeItem.rangePoints) - v)
            minData = QDateTime.fromMSecsSinceEpoch(
                self.zoomSeries.at(minData).x())
            maxData = QDateTime.fromMSecsSinceEpoch(
                self.zoomSeries.at(maxData).x())

        if minData > maxData:
            if self.RangeItem.handleSelected is None:
                self.resetView()
        else:
            self.BtnsWidget.dateRangeEdit.setDate([
                minData.toString("yyyy-MM-dd"),
                maxData.toString("yyyy-MM-dd")
            ])
            if self.relationState:
                self.rangeSig.emit([
                    minData.toString("yyyy-MM-dd HH:mm:ss"),
                    maxData.toString("yyyy-MM-dd HH:mm:ss"),
                ])

    # Change positions of rectangles in scaling
    def resizeEvent(self, event):
        super().resizeEvent(event)
        rect = self.chart().plotArea()
        self.parentRect.setRect(rect)
        self.getRangePoints()
        topRight = self.chart().plotArea().topRight()
        x = int(topRight.x())
        y = int(topRight.y())
        self.BtnsWidget.setGeometry(QRect(x - 420, 0, 420, 23))
        if self.RangeItem.isVisible():
            self.restore_rubber_band()
            self.save_current_rubber_band()
            self.RangeItem.updateHandlesPos()
        else:
            self.RangeItem.setRect(self.parentRect.rect())
            self.RangeItem.show()
            self.RangeItem.setRect(self.parentRect.rect())
            self.save_current_rubber_band()
            self.RangeItem.updateHandlesPos()
        self.apply_nice_numbers()

    # Restore to original positions of rectangles
    def restore_rubber_band(self):
        view_top_left = self.chart_to_view_point(self.m_chartRectF.topLeft())
        view_bottom_right = self.chart_to_view_point(
            self.m_chartRectF.bottomRight())

        self.m_rubberBandOrigin = view_top_left
        height = self.chart().plotArea().height()
        rect = QRectF()
        rect.setTopLeft(view_top_left)
        rect.setBottomRight(view_bottom_right)
        rect.setHeight(height)
        self.RangeItem.setRect(rect)

    # Adjust display coordinates of axes automatically
    def apply_nice_numbers(self):
        axes_list = self.chart().axes()
        for value_axis in axes_list:
            if value_axis:
                pass
Example #12
0
class AerialWareWidget(QWidget):
    """Core of AerialWare. Place it into your application, and you're good to go.

	Constructor args:
		getResultsAfterCompletion -- signifies if it's needed to get results. Already set to True if you use AerialWare as a module.
	Signals:
		done -- emitted when user is done with the program.
	"""

    done = pyqtSignal()

    def __init__(self, getResultsAfterCompletion=False):
        super().__init__()
        # Set flag to return results after user is done with the program.
        self.getResultsAfterCompletion = getResultsAfterCompletion
        # Load UI from file
        loadUi(programPath + "/ui/form.ui", self)
        # Create scene
        self.scene = _QCustomScene()
        self.Image.setScene(self.scene)
        # Use antialiasing
        self.Image.setRenderHint(QPainter.Antialiasing)

        # Values from step 4. We need to set up something to change languages.
        self.maxHorizontal = self.maxVertical = 0

        # Set validators
        v = QDoubleValidator()
        v.setBottom(0.00000001)
        v.setNotation(QDoubleValidator.StandardNotation)
        v2 = QDoubleValidator()
        v2.setNotation(QDoubleValidator.StandardNotation)

        for k in self.__dict__:
            obj = self.__dict__[k]
            tp = type(obj)
            if tp == QLineEdit:
                obj.setValidator(v2)
            # And set font size of labels
            elif tp == QLabel:
                font = obj.font()
                font.setPointSize(11)
                obj.setFont(font)

        self.editZoom.setValidator(v)
        self.editRes.setValidator(v)
        self.editHeight.setValidator(v)
        self.xDelimiter.setValidator(v)
        self.yDelimiter.setValidator(v)

        # Connect events
        self.btnOpenImage.clicked.connect(self.__loadImage)
        self.btnNext.clicked.connect(self.__stepTwo)
        self.btnIncreaseZoom.clicked.connect(self.__increaseZoom)
        self.btnDecreaseZoom.clicked.connect(self.__decreaseZoom)
        self.editZoom.textEdited.connect(self.__setZoom)

        # Load languages
        langDir = programPath + "/lang/"
        self.lastLang = langDir + ".LastLang"
        for lang in os.listdir(langDir):
            ext = os.path.splitext(lang)
            if os.path.isfile(langDir + lang) and ext[1] == ".py":
                spec = importlib.util.spec_from_file_location(
                    "lang", langDir + lang)
                lang = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(lang)

                name = lang.name
                self.comboLang.addItem(name, lang)
        self.comboLang.currentIndexChanged.connect(self.__changeLanguage)

        # Try to use previously selected language or English.
        # English is a fallback language. If it's not found, display error message and exit.
        index = self.comboLang.findText("English")
        if index == -1:
            QMessageBox(
                QMessageBox.Critical, "AerialWare - Error",
                "English localization not found. It should be in file " +
                langDir +
                "english.py. AerialWare uses it as base localization. Please, download AerialWare again."
            ).exec()
            exit()

        self.comboLang.setCurrentIndex(index)
        self.lang = _LanguageChanger(
            self.comboLang.currentData())  # Create language changer

        try:
            file = open(self.lastLang, "r")
            index = self.comboLang.findText(file.readline())
            file.close()
            if index != -1:
                self.comboLang.setCurrentIndex(index)
        except:
            pass
        self.__changeLanguage()

        # Initialize step 1
        self.__stepOne()

    ##################
    def __stepOne(self):
        """Step 1 -- loading image.
		"""
        self.__disableItems()
        # If user opened image with this program
        try:
            self.__loadImage(argv[1])
        except IndexError:
            self.__loadImage(programPath + "/ui/img/logo.png")

    def loadImageFromFile(self, path: str):
        """Loads image from given path and jumps to Step 2
		"""
        img = QPixmap(path)
        self.__loadQPixmap(img)
        self.__stepTwo()

    def loadImageFromQImage(self, image: QImage):
        """Loads image from given QImage and jumps to Step 2
		"""
        self.__loadQPixmap(QPixmap.fromImage(image))
        self.__stepTwo()

    def loadImageFromQPixmap(self, pixmap: QPixmap):
        """Loads image from given QPixmap and jumps to Step 2
		"""
        self.__loadQPixmap(pixmap)
        self.__stepTwo()

    def __loadImage(self, path=None):
        # Open file dialog and get path or try to use user's path
        if path == None or not path:
            file = QFileDialog.getOpenFileName(
                self, self.lang.openImage, "",
                self.lang.images + " (*.jpg *.jpeg *.png *.bmp);;" +
                self.lang.allFiles + " (*)")[0]
        else:
            file = path

        # Create a pixmap from file
        img = QPixmap(file)
        self.__loadQPixmap(img)

    def __loadQPixmap(self, img: QPixmap):
        """Draws QPixmap or raises an error message
		"""
        if not img.isNull():
            self.scene.clear()
            item = QGraphicsPixmapItem(img)
            self.height = img.height()
            self.width = img.width()
            self.scene.addItem(item)
            self.__enableItems()
        elif file != "":
            self.__disableItems()
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle(self.lang.invalidImageTitle)
            msg.setText(self.lang.invalidImage)
            msg.exec_()

    ##################

    def __stepTwo(self):
        """Step 2 -- Needed only for first click. Step 3 does all the job.
		"""
        self.__turnPage()
        self.btnNext.disconnect()
        self.btnNext.clicked.connect(self.__stepThree)

    ##################

    def __stepThree(self):
        """Step 3 -- Do basically everything.
		"""

        # Validate data
        def setError(e):
            """Displays string e.
			"""
            self.lblDataError.setText("<html><head/><body><div>" +
                                      self.lang.errData + e +
                                      "</div></body></html>")

        # Try to read data. We need to create fields, because this will be used later in other functions.
        self.xTL = self.__sfloat(self.xTopLeft.text())
        self.xTR = self.__sfloat(self.xTopRight.text())
        self.xBL = self.__sfloat(self.xBottomLeft.text())
        self.xBR = self.__sfloat(self.xBottomRight.text())

        self.yTL = self.__sfloat(self.yTopLeft.text())
        self.yTR = self.__sfloat(self.yTopRight.text())
        self.yBL = self.__sfloat(self.yBottomLeft.text())
        self.yBR = self.__sfloat(self.yBottomRight.text())

        self.xD = self.__sfloat(self.xDelimiter.text())
        self.yD = self.__sfloat(self.yDelimiter.text())

        # Convert delimiters to pixels.
        # Value of another coordinate doesn't matter.
        # If you'll draw a random line and draw crossing lines parallel to one of axes with same distance between these lines by another axis, you'll split your random line into equal pieces.
        # See for yourself:
        # Y
        # ^ ___\_____
        # | ____\____
        # | _____\___
        # |       \
        # |-----------> X
        # You can try it on paper or prove this "theorem" doing some math.
        try:
            self.xDTop = self.width / abs(self.xTL - self.xTR) * self.xD
            self.xDBottom = self.width / abs(self.xBL - self.xBR) * self.xD
            self.yDLeft = self.height / abs(self.yTL - self.yBL) * self.yD
            self.yDRight = self.height / abs(self.yTR - self.yBR) * self.yD
        except ZeroDivisionError:
            setError(self.lang.errCorners)
            return

        # Now do the same stuff, but find how much pixels in one degree.
        # We won't find absolute value for subtraction because axes of image in geographic system may be not codirectional to axes of image in Qt system.
        self.topX = (self.xTR - self.xTL) / self.width
        self.bottomX = (self.xBR - self.xBL) / self.width
        self.leftY = (self.yTL - self.yBL) / self.height
        self.rightY = (self.yTR - self.yBR) / self.height

        # Sides of image in geographic system
        self.top = QLineF(self.xTL, self.yTL, self.xTR, self.yTR)
        self.bottom = QLineF(self.xBL, self.yBL, self.xBR, self.yBR)
        self.left = QLineF(self.xTL, self.yTL, self.xBL, self.yBL)
        self.right = QLineF(self.xTR, self.yTR, self.xBR, self.yBR)

        errors = ""
        if self.xDTop > self.width:
            errors += self.lang.errLongTop + "<br>"
        if self.xDBottom > self.width:
            errors += self.lang.errLongBottom + "<br>"
        if self.yDLeft > self.height:
            errors += self.lang.errLatLeft + "<br>"
        if self.yDRight > self.height:
            errors += self.lang.errLatRight + "<br>"

        if errors != "":
            setError(self.lang.errSides + "<br>" + errors)
            return

        # Check if given coordinates form 8-shaped figure
        if (self.xTL > self.xTR and self.xBL < self.xBR) or (
                self.xTL < self.xTR and self.xBL > self.xBR) or (
                    self.yTL > self.yBL
                    and self.yTR < self.yBR) or (self.yTL < self.yBL
                                                 and self.yTR > self.yTL):
            choice = QMessageBox(QMessageBox.Warning, "AerialWare",
                                 self.lang.warningCoordinates,
                                 QMessageBox.Yes | QMessageBox.No).exec()
            if choice == QMessageBox.No:
                return

        # Draw grid

        # Set points for grid
        # Points will look like:
        # [point, point, ...],
        # [point, point, ...], ...
        pointRows = []
        # Let x1, y1; x2, y2 be vertical line
        # and x3, y3; x4, y4 be horizontal line.
        # Thus, y1 and y2 should be always on top and bottom;
        # x3, x4 -- on left and right
        y1, y2, x3, x4 = 0, self.height, 0, self.width
        # So we have to change:
        #	for vertical line: x1, x2
        #	for horizontal line: y3, y4

        y3 = y4 = 0  # Initial values for horizontal line
        # Move horizontal line
        while y3 <= self.height + self.yDLeft / 2 or y4 <= self.height + self.yDRight / 2:
            x1 = x2 = 0  # Initial values for vertical line
            # Move vertical line
            points = []
            while x1 <= self.width + self.xDTop / 2 or x2 <= self.width + self.xDBottom / 2:
                point = QPointF()
                QLineF.intersect(QLineF(x1, y1, x2, y2),
                                 QLineF(x3, y3, x4, y4), point)
                points.append(point)
                x1 += self.xDTop
                x2 += self.xDBottom
            if points != []:
                pointRows.append(points)
            y3 += self.yDLeft
            y4 += self.yDRight

        # If nothing has been added
        if points == []:
            setError(self.lang.errPoints)
            return

        # Get scene geometry to preserve scene expanding
        rect = self.scene.sceneRect()
        # And add bounds for the grid
        self.bounds = QGraphicsRectItem(rect)
        self.bounds.setFlag(self.bounds.ItemClipsChildrenToShape)
        self.scene.addItem(self.bounds)
        # Create polygons from points
        # We'll recheck previous item
        i = 1  # Rows
        while i < len(pointRows):
            j = 1  # Points
            while j < len(pointRows[i]):
                # Add points in following order: top left, top right, bottom right, bottom left
                points = [
                    pointRows[i - 1][j - 1], pointRows[i - 1][j],
                    pointRows[i][j], pointRows[i][j - 1]
                ]
                # We're assigning self.bounds as parent implicitly, so we shouldn't add polygon to scene by ourselves.
                poly = _QCustomGraphicsPolygonItem(QPolygonF(points),
                                                   self.bounds)
                poly.setRowCol(i - 1, j - 1)
                j += 1
            i += 1
        # Restore scene geometry
        self.scene.setSceneRect(rect)

        self.__turnPage()
        self.btnNext.disconnect()
        self.btnNext.clicked.connect(self.__stepFour)

    ##################

    def __stepFour(self):
        """Step 4 -- Do hardware calculations.
		"""
        # If nothing has been selected, display error message.
        if self.scene.selectedItems() == []:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("AerialWare")
            msg.setText(self.lang.errEmptySelection)
            msg.exec_()
            return

        self.__turnPage()
        self.scene.setEnabled(False)

        # Find biggest vertical and horizontal lines of bounding rect of every polygon
        for square in self.scene.selectedItems():
            pointsOrig = square.polygon()
            pointsConv = []
            for point in pointsOrig:
                pointsConv.append(self.pxToDeg(point.x(), point.y()))
            points = QPolygonF(QPolygonF(pointsConv).boundingRect())

            # Because top = bottom and left = right we can use only one of each side for comparison.
            # Points of polygon goes clockwise from top left corner.
            lenTop = self.__lenMeters(points[0], points[1])
            lenRight = self.__lenMeters(points[1], points[2])

            self.maxHorizontal = max(self.maxHorizontal, lenTop)
            self.maxVertical = max(self.maxVertical, lenRight)

        self.__changeLanguage(
        )  # Need to change language because we need to insert max values into caption

        # Connect slots for calculations
        self.editRes.textEdited.connect(self.__calculateResolution)
        self.editHeight.textEdited.connect(self.__calculateFocalLength)

        # Call this slots to fill values with zeros
        self.__calculateResolution()
        self.__calculateFocalLength()

        # Change text on 'Next' button and connect it to 'save' method.
        if self.getResultsAfterCompletion:
            nextText = self.lang.done
        else:
            nextText = self.lang.save
        self.btnNext.setText(nextText)
        self.btnNext.disconnect()
        self.btnNext.clicked.connect(self.__save)

    def __calculateResolution(self):
        """Calculates camera resolution based on given deg/px ratio.
		"""
        self.camRatio = self.__sfloat(self.editRes.text())
        try:
            self.camWidth = int(self.maxHorizontal / self.camRatio)
            self.camHeight = int(self.maxVertical / self.camRatio)
        except ZeroDivisionError:
            self.camWidth = 0
            self.camHeight = 0
        self.lblCamRes.setText(f"{self.camWidth}x{self.camHeight}")

    def __calculateFocalLength(self):
        """Calculates focal length based on given flight height.
		"""
        self.flightHeight = self.__sfloat(self.editHeight.text())
        try:
            self.focalLength = self.flightHeight / (self.camRatio * 1000)
        except ZeroDivisionError:
            self.focalLength = 0
        self.lblFocalResult.setText(str(self.focalLength))

    ##################

    def __save(self):
        """If AerialWare has been used as a module, emits signal 'done'. Saves user results into SVG and makes report otherwise.
		"""
        # Variables for report
        self.pointsMeridian = ""
        self.pointsHorizontal = ""

        # Total lengths of paths. Used in report and methods.
        self.lenMeridian = 0
        self.lenMeridianWithTurns = 0
        self.lenHorizontal = 0
        self.lenHorizontalWithTurns = 0

        # Fields for methods
        self.pathMeridianPointsPx = []
        self.pathMeridianPointsDeg = []
        self.pathMeridianLinesPx = []
        self.pathMeridianLinesWithTurnsPx = []
        self.pathMeridianLinesDeg = []
        self.pathMeridianLinesWithTurnsDeg = []

        self.pathHorizontalPointsPx = []
        self.pathHorizontalPointsDeg = []
        self.pathHorizontalLinesPx = []
        self.pathHorizontalLinesWithTurnsPx = []
        self.pathHorizontalLinesDeg = []
        self.pathHorizontalLinesWithTurnsDeg = []

        # Process each line
        def processLines(lines, isMeridian=False):
            i = 2  # Counter for report
            isEven = False  # If current line is even we must swap it's points.
            for line in lines:
                linePx = line.line()
                p1 = linePx.p1()
                p2 = linePx.p2()
                p1Deg = self.pxToDeg(p1.x(), p1.y())
                p2Deg = self.pxToDeg(p2.x(), p2.y())
                lineDeg = QLineF(p1Deg, p2Deg)
                lineLength = self.__lenMeters(p1Deg, p2Deg)

                if isMeridian:
                    self.pathMeridianLinesWithTurnsPx.append(linePx)
                    self.pathMeridianLinesWithTurnsDeg.append(lineDeg)
                    self.lenMeridianWithTurns += lineLength
                else:
                    self.pathHorizontalLinesWithTurnsPx.append(linePx)
                    self.pathHorizontalLinesWithTurnsDeg.append(lineDeg)
                    self.lenHorizontalWithTurns += lineLength

                if line.pen().style(
                ) == Qt.SolidLine:  # Check if current line doesn't represent turn
                    if isEven:
                        p1, p2, p1Deg, p2Deg = p2, p1, p2Deg, p1Deg

                    point = f'"{i - 1}","{p1Deg.x()}","{p1Deg.y()}"\n' + f'"{i}","{p2Deg.x()}","{p2Deg.y()}"\n'
                    if isMeridian:
                        self.pointsMeridian += point
                        self.pathMeridianPointsPx.extend([p1, p2])
                        self.pathMeridianPointsDeg.extend([p1Deg, p2Deg])
                        self.pathMeridianLinesPx.append(linePx)
                        self.pathMeridianLinesDeg.append(lineDeg)
                        self.lenMeridian += lineLength
                    else:
                        self.pointsHorizontal += point
                        self.pathHorizontalPointsPx.extend([p1, p2])
                        self.pathHorizontalPointsDeg.extend([p1Deg, p2Deg])
                        self.pathHorizontalLinesPx.append(linePx)
                        self.pathHorizontalLinesDeg.append(lineDeg)
                        self.lenHorizontal += lineLength
                    isEven = not isEven
                    i += 2

        processLines(self.scene.getMeridianLines(), True)
        processLines(self.scene.getHorizontalLines())

        if self.getResultsAfterCompletion:
            self.done.emit()
            return

        self.__disableItems()

        # Make report
        pointHeader = f'"{self.lang.repPoint}","{self.lang.lblLatitude}","{self.lang.lblLongitude}"\n'

        if self.lenHorizontalWithTurns > self.lenMeridianWithTurns:
            directionWithTurns = self.lang.repFlyMeridians
        elif self.lenHorizontalWithTurns < self.lenMeridianWithTurns:
            directionWithTurns = self.lang.repFlyHorizontals
        else:
            directionWithTurns = self.lang.repFlyEqual

        if self.lenHorizontal > self.lenMeridian:
            directionWithoutTurns = self.lang.repFlyMeridians
        elif self.lenHorizontal < self.lenMeridian:
            directionWithoutTurns = self.lang.repFlyHorizontals
        else:
            directionWithoutTurns = self.lang.repFlyEqual

        report = (
            f'"{self.lang.repCornersDescription}"\n'
            f'"{self.lang.lblCorner}","{self.lang.lblLongitude}","{self.lang.lblLatitude}"\n'
            f'"{self.lang.lblTopLeft}","{self.xTL}","{self.yTL}"\n'
            f'"{self.lang.lblTopRight}","{self.xTR}","{self.yTR}"\n'
            f'"{self.lang.lblBottomLeft}","{self.xBL}","{self.yBL}"\n'
            f'"{self.lang.lblBottomRight}","{self.xBR}","{self.yBR}"\n'
            f'"{self.lang.lblDelimiters}","{self.xD}","{self.yD}"\n\n'
            f'"{self.lang.repTotalWithTurns}"\n'
            f'"{self.lang.repByMeridians}","{self.lenMeridianWithTurns}"\n'
            f'"{self.lang.repByHorizontals}","{self.lenHorizontalWithTurns}"\n'
            f'"{self.lang.repBetterFlyBy}","{directionWithTurns}"\n\n'
            f'"{self.lang.repTotalWithoutTurns}"\n'
            f'"{self.lang.repByMeridians}","{self.lenMeridian}"\n'
            f'"{self.lang.repByHorizontals}","{self.lenHorizontal}"\n'
            f'"{self.lang.repBetterFlyBy}","{directionWithoutTurns}"\n\n'
            f'"{self.lang.repAerialParams}"\n'
            f'"{self.lang.repArea}","{self.maxHorizontal}","x","{self.maxVertical}"\n'
            f'"{self.lang.lblDesiredRes}","{self.camRatio}"\n'
            f'"{self.lang.lblRes}","{self.camWidth}","x","{self.camHeight}"\n'
            f'"{self.lang.lblHeight}","{self.flightHeight}"\n'
            f'"{self.lang.lblFocal}","{self.focalLength}"\n\n'
            f'"{self.lang.repMeridianPoints}"\n' + pointHeader +
            self.pointsMeridian + f'\n"{self.lang.repHorizontalPoints}"\n' +
            pointHeader + self.pointsHorizontal)

        # Save image
        self.__disableItems()
        file = QFileDialog.getSaveFileName(self, self.lang.saveFile, "",
                                           self.lang.vectorImage +
                                           " (*.svg)")[0]
        if file == "":
            self.__enableItems()
            return

        # And choose where to save report
        reportName = ""
        while reportName == "":
            reportName = QFileDialog.getSaveFileName(
                self, self.lang.saveFile, "", self.lang.table + " (*.csv)")[0]
            if reportName == "":
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText(self.lang.errSaveBoth)
                msg.exec_()

        rect = self.scene.sceneRect()
        gen = QSvgGenerator()
        gen.setFileName(file)
        gen.setSize(rect.size().toSize())
        gen.setViewBox(rect)
        gen.setTitle("Flight paths generated by AerialWare")
        gen.setDescription(gen.title())

        # Notice: painting will temporarily freeze application because QGraphicsScene::render is not thread safe.
        # Don't try putting this into python's threads and QThread, doesn't work, I've tried, trust me XD
        painter = QPainter(gen)
        self.scene.render(painter)
        painter.end()

        # Save report
        reportFile = open(reportName, "w")
        reportFile.write(report)
        reportFile.close()

        self.__enableItems()

    ##################

    # API

    def getPathByMeridiansPointsPx(self):
        """Returns list with points in pixels representing flight path by meridians.
		Please note: all points are sorted as a plane should fly.
		"""
        return self.pathMeridianPointsPx

    def getPathByMeridiansPointsDeg(self):
        """Returns list with points in geographic coordinate system representing flight path by meridians.
		Please note: all points are sorted as a plane should fly.
		Looks like: [QPointF(long, lat), QPointF(long, lat), ...]
		"""
        return self.pathMeridianPointsDeg

    def getPathByMeridiansLinesPx(self):
        """Returns list with lines in pixels coordinates without turns representing flight path by meridians.
		Please note: all points of lines are sorted as a plane should fly.
		"""
        return self.pathMeridianLinesPx

    def getPathByMeridiansLinesWithTurnsPx(self):
        """Returns list with lines in pixels with turns representing flight path by meridians.
		Please note: points of lines are NOT sorted as a plane should fly. You can use even lines, as they're representing turns, to sort things out. Or just get path without turns.
		"""
        return self.pathMeridianLinesWithTurnsPx

    def getPathByMeridiansLinesDeg(self):
        """Returns list with lines in degrees without turns representing flight path by meridians.
		Please note: all points of lines are sorted as a plane should fly.
		"""
        return self.pathMeridianLinesDeg

    def getPathByMeridiansLinesWithTurnsDeg(self):
        """Returns list with lines in degrees with turns representing flight path by meridians.
		Please note: points of lines are NOT sorted as a plane should fly. You can use even lines, as they're representing turns, to sort things out. Or just get path without turns.
		"""
        return self.pathMeridianLinesWithTurnsDeg

    # Horizontals
    def getPathByHorizontalsPointsPx(self):
        """Returns list with points in pixels representing flight path by horizontals.
		"""
        return self.pathHorizontalPointsPx

    def getPathByHorizontalsPointsDeg(self):
        """Returns list with points in geographic coordinate system representing flight path by horizontals.
		Looks like: [QPointF(long, lat), QPointF(long, lat), ...]
		"""
        return self.pathHorizontalPointsDeg

    def getPathByHorizontalsLinesPx(self):
        """Returns list with lines in pixels coordinates without turns representing flight path by horizontals.
		"""
        return self.pathHorizontalLinesPx

    def getPathByHorizontalsLinesWithTurnsPx(self):
        """Returns list with lines in pixels with turns representing flight path by horizontals.
		"""
        return self.pathHorizontalLinesWithTurnsPx

    def getPathByHorizontalsLinesDeg(self):
        """Returns list with lines in degrees without turns representing flight path by horizontals.
		"""
        return self.pathHorizontalLinesDeg

    def getPathByHorizontalsLinesWithTurnsDeg(self):
        """Returns list with lines in degrees with turns representing flight path by horizontals.
		"""
        return self.pathHorizontalLinesWithTurnsDeg

    # Lengths
    def getPathLengthByMeridians(self):
        """Returns length of path by meridians without turns in meters.
		"""
        return self.lenMeridian

    def getPathLengthByMeridiansWithTurns(self):
        """Returns length of path by meridians with turns in meters. This value is approximate.
		"""
        return self.lenMeridianWithTurns

    def getPathLengthByHorizontals(self):
        """Returns length of path by horizontal without turns in meters.
		"""
        return self.lenHorizontal

    def getPathLengthByHorizontalsWithTurns(self):
        """Returns length of path by horizontal with turns in meters. This value is approximate.
		"""
        return self.lenHorizontalWithTurns

    # Aerial parameters

    def getMaxArea(self):
        """Returns maximum area in meters to be captured in dict: {"w": width, "h": height}
		"""
        return {"w": self.maxHorizontal, "h": self.maxVertical}

    def getCameraRatio(self):
        """Returns m/px ratio entered by user.
		"""
        return self.camRatio

    def getCameraResolution(self):
        """Returns camera resolution in dict: {"w": width, "h": height}
		"""
        return {"w": self.camWidth, "h": self.camHeight}

    def getFlightHeight(self):
        """Returns flight height in meters.
		"""
        return self.flightHeight

    def getFocalLength(self):
        """Returns focal length in mm.
		"""
        return self.focalLength

    def pxToDeg(self, x, y):
        """Transforms pixel coordinates of point to Geographic coordinate system.
		Args:
			x, y -- X and Y coordinates of point to process.
		Returns:
			QPointF(long, lat) -- longitude and latitude coordinates of given point
		"""
        # Please check comments in __stepThree() where we converting step in degrees to pixels in order to understand what we're doing here.
        # Convert given coordinates to degrees relative to the sides.
        topX = self.xTL + self.topX * x
        bottomX = self.xBL + self.bottomX * x
        # We subtract because Y and lat are not codirectional by default
        leftY = self.yTL - self.leftY * y
        rightY = self.yTR - self.rightY * y

        # Find another coordinate for each side by drawing straight line with calculated coordinate for both points.
        # Intersection of this line and side will give needed point.
        # Looks like this:
        # Y
        # ^
        # | \ <- This is side
        # |  \
        # |---*-----  <- This line is crossing point relative to the side
        # |    \
        # |     \
        # |------------------> X
        # Containers for points and result.
        top, bottom, left, right, res = QPointF(), QPointF(), QPointF(
        ), QPointF(), QPointF()

        QLineF.intersect(QLineF(topX, 0, topX, 1), self.top, top)
        QLineF.intersect(QLineF(bottomX, 0, bottomX, 1), self.bottom, bottom)
        QLineF.intersect(QLineF(0, leftY, 1, leftY), self.left, left)
        QLineF.intersect(QLineF(0, rightY, 1, rightY), self.right, right)

        # We've got coordinates for each side where given point should lie.
        # Let's draw lines throgh them like this:
        #  ________________________
        # |    \                   |
        # |_____\__________________|
        # |      \                 |
        # |_______\________________|
        # Lines are drawn in geographic system, not in pixels.
        # Their intersection will return given point in geographic system.
        QLineF.intersect(QLineF(top, bottom), QLineF(left, right), res)
        return res

    def __lenMeters(self, p1, p2):
        """Calculates length in meters of line in geographic system using haversine formula.
		Args:
			QPointF p1, p2 -- points of line.
		"""
        f1, f2 = math.radians(p1.y()), math.radians(p2.y())
        df = f2 - f1
        dl = math.radians(p2.x() - p1.x())
        a = math.sin(
            df / 2)**2 + math.cos(f1) * math.cos(f2) * math.sin(dl / 2)**2
        # First value is radius of Earth
        return 6371000 * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))

    ##################

    # Misc stuff

    def __enableItems(self):
        """Enables controls
		"""
        self.scene.setEnabled(True)
        self.editZoom.setEnabled(True)
        self.btnDecreaseZoom.setEnabled(True)
        self.btnIncreaseZoom.setEnabled(True)
        self.btnNext.setEnabled(True)

    def __disableItems(self):
        """Disables controls
		"""
        self.scene.setEnabled(False)
        self.editZoom.setEnabled(False)
        self.btnDecreaseZoom.setEnabled(False)
        self.btnIncreaseZoom.setEnabled(False)
        self.btnNext.setEnabled(False)

    def __changeLanguage(self):
        """Changes app language. Uses _LanguageChanger, check it's description for more.
		"""
        # Get language
        self.lang.setLanguage(self.comboLang.currentData())
        langName = self.comboLang.currentText()

        # Save this language to file
        file = open(self.lastLang, "w")
        file.write(langName)
        file.close()

        # Change text of labels and buttons
        self.lblZoom.setText(self.lang.lblZoom)
        self.lblCorner.setText(self.lang.lblCorner)
        self.lblLongitude.setText(self.lang.lblLongitude)
        self.lblLatitude.setText(self.lang.lblLatitude)
        self.lblTopLeft.setText(self.lang.lblTopLeft)
        self.lblTopRight.setText(self.lang.lblTopRight)
        self.lblBottomLeft.setText(self.lang.lblBottomLeft)
        self.lblBottomRight.setText(self.lang.lblBottomRight)
        self.lblDelimiters.setText(self.lang.lblDelimiters)
        self.lblRes.setText(self.lang.lblRes)
        self.lblDesiredRes.setText(self.lang.lblDesiredRes)
        self.lblHeight.setText(self.lang.lblHeight)
        self.lblFocal.setText(self.lang.lblFocal)
        self.btnOpenImage.setText(self.lang.btnOpenImage)

        # Change text of task labels
        start = "<html><head/><body>"
        end = "</body></html>"

        self.lblTask1.setText(start + f"""
			<p style='text-align: center;'><b>{self.lang.heading}</b></p>
			<p><b>{self.lang.headingAbout}</b></p>
			<p>{self.lang.about1}</p>
			<p>{self.lang.about2}</p>
			<p><b>{self.lang.workingTitle}</b></p>
			<p>{self.lang.working}</p>
			<p><b>{self.lang.thisStepBold}</b>{self.lang.thisStep}</p>
			<p><b>{self.lang.noteStep1Bold}</b>{self.lang.noteStep1}</p>
			""" + end)

        self.lblTask2.setText(start + f"""
			<p><b>{self.lang.setTitle}</b></p>
			<ul>
				<li>{self.lang.coordinates}</li>
				<li>{self.lang.delimiters}</li>
			</ul>
		""" + end)

        self.lblTask3.setText(start + f"""
			<p>{self.lang.intro}<b>{self.lang.clickBold}</b></p>
			<p>{self.lang.path}</p>
			<p><b>{self.lang.legendTitle}</b></p>
			<ul>
				<li><span style="color: red;">{self.lang.red}</span>{self.lang.line1}<span style="color: green;">{self.lang.green}</span>{self.lang.line2}</li>
				<li><b>{self.lang.dashedBold}</b>{self.lang.line3}</li>
			</ul>
		""" + end)

        if self.getResultsAfterCompletion:
            btnText = self.lang.done
            s4Text = self.lang.s4Done
        else:
            btnText = self.lang.save
            s4Text = start + f"""
			<p>{self.lang.s4Save}</p>
			<p><b>{self.lang.noteStep4Bold}</b>{self.lang.noteStep4} ¯\_(ツ)_/¯</p>
			""" + end

        if self.Steps.currentIndex() == self.Steps.count():
            self.btnNext.setText(btnText)
        else:
            self.btnNext.setText(self.lang.btnNext)

        self.lblTask4_1.setText(
            f"{self.lang.s4Text1P1}{self.maxHorizontal}x{self.maxVertical}{self.lang.s4Text1P2}"
        )
        self.lblTask4_2.setText(self.lang.s4Text2)
        self.lblTask4_3.setText(s4Text)

        # On Step 3 there are dynamically outputed errors. In order to update it we can manually reset it or just kill two birds with one stone by recalling this step in cost of couple milliseconds.
        if self.Steps.currentIndex() == 1 and self.lblDataError.text() != "":
            self.__stepThree()

    def __turnPage(self):
        """Turns page of "Steps"
		"""
        self.Steps.setCurrentIndex(self.Steps.currentIndex() + 1)

    def __increaseZoom(self):
        """Zooms image in
		"""
        value = round(self.__sfloat(self.editZoom.text()) + 10, 2)
        self.editZoom.setText(str(value))
        self.__setZoom()

    def __decreaseZoom(self):
        """Zooms image out
		"""
        value = self.__sfloat(self.editZoom.text())
        if value <= 10:
            value = round(value / 2, 2)
        elif value <= 0:
            return
        else:
            value -= 10
        self.editZoom.setText(str(value))
        self.__setZoom()

    def __setZoom(self):
        """Sets zoom
		"""
        value = self.__sfloat(self.editZoom.text()) * 0.01
        self.Image.resetTransform()
        self.Image.scale(value, value)

    def __sfloat(self, s):
        """Converts string to float. If can't convert will return 0.0. Used as shorthand.
		"""
        try:
            ss = float(s)
        except:
            return 0.0
        return ss
Example #13
0
class CameraView(QGraphicsObject):

    font: QFont = QFont("monospace", 16)
    stick_link_requested = pyqtSignal(StickWidget)
    stick_context_menu = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject')
    stick_widgets_out_of_sync = pyqtSignal('PyQt_PyObject')
    visibility_toggled = pyqtSignal()
    synchronize_clicked = pyqtSignal('PyQt_PyObject')
    previous_photo_clicked = pyqtSignal('PyQt_PyObject')
    next_photo_clicked = pyqtSignal('PyQt_PyObject')
    sync_confirm_clicked = pyqtSignal('PyQt_PyObject')
    sync_cancel_clicked = pyqtSignal('PyQt_PyObject')
    first_photo_clicked = pyqtSignal('PyQt_PyObject')
    enter_pressed = pyqtSignal()

    def __init__(self, scale: float, parent: Optional[QGraphicsItem] = None):
        QGraphicsObject.__init__(self, parent)
        self.current_highlight_color = QColor(0, 0, 0, 0)
        self.current_timer = -1
        self.scaling = scale
        self.pixmap = QGraphicsPixmapItem(self)
        self.stick_widgets: List[StickWidget] = []
        self.link_cam_text = QGraphicsSimpleTextItem("Link camera...", self)
        self.link_cam_text.setZValue(40)
        self.link_cam_text.setVisible(False)
        self.link_cam_text.setFont(CameraView.font)
        self.link_cam_text.setPos(0, 0)
        self.link_cam_text.setPen(QPen(QColor(255, 255, 255, 255)))
        self.link_cam_text.setBrush(QBrush(QColor(255, 255, 255, 255)))

        self.show_add_buttons = False
        self.camera = None

        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.show_stick_widgets = False
        self.setAcceptHoverEvents(True)
        self.stick_edit_mode = False

        self.original_pixmap = self.pixmap.pixmap()

        self.hovered = False

        self.mode = 0  # TODO make enum Mode
        self.click_handler = None
        self.double_click_handler: Callable[[int, int], None] = None

        self.stick_widget_mode = StickMode.Display

        self.highlight_animation = QPropertyAnimation(self, b"highlight_color")
        self.highlight_animation.setEasingCurve(QEasingCurve.Linear)
        self.highlight_animation.valueChanged.connect(
            self.handle_highlight_color_changed)
        self.highlight_rect = QGraphicsRectItem(self)
        self.highlight_rect.setZValue(4)
        self.highlight_rect.setPen(QPen(QColor(0, 0, 0, 0)))
        self.title_btn = Button('btn_title', '', parent=self)
        self.title_btn.setFlag(QGraphicsItem.ItemIgnoresTransformations, False)
        self.title_btn.setZValue(5)
        self.title_btn.setVisible(False)

        self.sticks_without_width: List[Stick] = []
        self.current_image_name: str = ''
        self.control_widget = ControlWidget(parent=self)
        self.control_widget.setFlag(QGraphicsItem.ItemIgnoresTransformations,
                                    False)
        self.control_widget.setVisible(True)
        self._connect_control_buttons()
        self.image_available = True
        self.blur_eff = QGraphicsBlurEffect()
        self.blur_eff.setBlurRadius(5.0)
        self.blur_eff.setEnabled(False)
        self.pixmap.setGraphicsEffect(self.blur_eff)
        self.overlay_message = QGraphicsSimpleTextItem('not available',
                                                       parent=self)
        font = self.title_btn.font
        font.setPointSize(48)
        self.overlay_message.setFont(font)
        self.overlay_message.setBrush(QBrush(QColor(200, 200, 200, 200)))
        self.overlay_message.setPen(QPen(QColor(0, 0, 0, 200), 2.0))
        self.overlay_message.setVisible(False)
        self.overlay_message.setZValue(6)

        self.stick_box = QGraphicsRectItem(parent=self)
        self.stick_box.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.stick_box.setVisible(False)
        self.stick_box_start_pos = QPoint()

    def _connect_control_buttons(self):
        self.control_widget.synchronize_btn.clicked.connect(
            lambda: self.synchronize_clicked.emit(self))
        self.control_widget.prev_photo_btn.clicked.connect(
            lambda: self.previous_photo_clicked.emit(self))
        self.control_widget.next_photo_btn.clicked.connect(
            lambda: self.next_photo_clicked.emit(self))
        self.control_widget.accept_btn.clicked.connect(
            lambda: self.sync_confirm_clicked.emit(self))
        self.control_widget.cancel_btn.clicked.connect(
            lambda: self.sync_cancel_clicked.emit(self))
        self.control_widget.first_photo_btn.clicked.connect(
            lambda: self.first_photo_clicked.emit(self))

    def paint(self, painter: QPainter,
              option: PyQt5.QtWidgets.QStyleOptionGraphicsItem,
              widget: QWidget):
        if self.pixmap.pixmap().isNull():
            return
        painter.setRenderHint(QPainter.Antialiasing, True)

        if self.show_stick_widgets:
            brush = QBrush(QColor(255, 255, 255, 100))
            painter.fillRect(self.boundingRect(), brush)

        if self.mode and self.hovered:
            pen = QPen(QColor(0, 125, 200, 255))
            pen.setWidth(4)
            painter.setPen(pen)

    def boundingRect(self) -> PyQt5.QtCore.QRectF:
        return self.pixmap.boundingRect().united(
            self.title_btn.boundingRect().translated(self.title_btn.pos()))

    def initialise_with(self, camera: Camera):
        if self.camera is not None:
            self.camera.stick_added.disconnect(self.handle_stick_created)
            self.camera.sticks_added.disconnect(self.handle_sticks_added)
            self.camera.stick_removed.disconnect(self.handle_stick_removed)
            self.camera.sticks_removed.disconnect(self.handle_sticks_removed)
            self.camera.stick_changed.disconnect(self.handle_stick_changed)
        self.camera = camera
        self.prepareGeometryChange()
        self.set_image(camera.rep_image, Path(camera.rep_image_path).name)
        self.title_btn.set_label(self.camera.folder.name)
        self.title_btn.set_height(46)
        self.title_btn.fit_to_contents()
        self.title_btn.set_width(int(self.boundingRect().width()))
        self.title_btn.setPos(0, self.boundingRect().height())
        self.control_widget.title_btn.set_label(self.camera.folder.name)
        self.camera.stick_added.connect(self.handle_stick_created)
        self.camera.sticks_added.connect(self.handle_sticks_added)
        self.camera.stick_removed.connect(self.handle_stick_removed)
        self.camera.sticks_removed.connect(self.handle_sticks_removed)
        self.camera.stick_changed.connect(self.handle_stick_changed)

        self.control_widget.set_font_height(32)
        self.control_widget.set_widget_height(
            self.title_btn.boundingRect().height())
        self.control_widget.set_widget_width(int(self.boundingRect().width()))
        self.control_widget.setPos(0,
                                   self.pixmap.boundingRect().height()
                                   )  #self.boundingRect().height())
        self.control_widget.set_mode('view')
        self.update_stick_widgets()

    def set_image(self,
                  img: Optional[np.ndarray] = None,
                  image_name: Optional[str] = None):
        if img is None:
            self.show_overlay_message('not available')
            return
        self.show_overlay_message(None)
        self.prepareGeometryChange()
        barray = QByteArray(img.tobytes())
        image = QImage(barray, img.shape[1], img.shape[0],
                       QImage.Format_BGR888)
        self.original_pixmap = QPixmap.fromImage(image)
        self.pixmap.setPixmap(self.original_pixmap)
        self.highlight_rect.setRect(self.boundingRect())
        self.current_image_name = image_name

    def update_stick_widgets(self):
        stick_length = 60
        for stick in self.camera.sticks:
            sw = StickWidget(stick, self.camera, self)
            sw.set_mode(self.stick_widget_mode)
            self.connect_stick_widget_signals(sw)
            self.stick_widgets.append(sw)
            stick_length = stick.length_cm
        self.update_stick_box()
        self.scene().update()

    def scale_item(self, factor: float):
        self.prepareGeometryChange()
        pixmap = self.original_pixmap.scaledToHeight(
            int(self.original_pixmap.height() * factor))
        self.pixmap.setPixmap(pixmap)
        self.__update_title()

    def set_show_stick_widgets(self, value: bool):
        for sw in self.stick_widgets:
            sw.setVisible(value)
        self.scene().update()

    def hoverEnterEvent(self, e: QGraphicsSceneHoverEvent):
        self.hovered = True
        self.scene().update(self.sceneBoundingRect())

    def hoverLeaveEvent(self, e: QGraphicsSceneHoverEvent):
        self.hovered = False
        self.scene().update(self.sceneBoundingRect())

    def mousePressEvent(self, e: QGraphicsSceneMouseEvent):
        super().mousePressEvent(e)

    def mouseReleaseEvent(self, e: QGraphicsSceneMouseEvent):
        if self.mode == 1:
            self.click_handler(self.camera)

    def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent):
        if self.stick_widget_mode == StickMode.EditDelete:
            x = event.pos().toPoint().x()
            y = event.pos().toPoint().y()
            stick = self.camera.create_new_sticks(
                [(np.array([[x, y - 50], [x, y + 50]]), 3)],
                self.current_image_name)[
                    0]  #self.dataset.create_new_stick(self.camera)
            self.sticks_without_width.append(stick)

    def set_button_mode(self, click_handler: Callable[[Camera], None],
                        data: str):
        self.mode = 1  # TODO make a proper ENUM
        self.click_handler = lambda c: click_handler(c, data)

    def set_display_mode(self):
        self.mode = 0  # TODO make a proper ENUM
        self.click_handler = None

    def _remove_stick_widgets(self):
        for sw in self.stick_widgets:
            sw.setParentItem(None)
            self.scene().removeItem(sw)
            sw.deleteLater()
        self.stick_widgets.clear()

    def handle_stick_created(self, stick: Stick):
        if stick.camera_id != self.camera.id:
            return
        sw = StickWidget(stick, self.camera, self)
        sw.set_mode(self.stick_widget_mode)
        self.connect_stick_widget_signals(sw)
        self.stick_widgets.append(sw)
        self.stick_widgets_out_of_sync.emit(self)
        self.update()

    def handle_stick_removed(self, stick: Stick):
        if stick.camera_id != self.camera.id:
            return
        stick_widget = next(
            filter(lambda sw: sw.stick.id == stick.id, self.stick_widgets))
        self.disconnect_stick_widget_signals(stick_widget)
        self.stick_widgets.remove(stick_widget)
        stick_widget.setParentItem(None)
        self.scene().removeItem(stick_widget)
        stick_widget.deleteLater()
        self.update()

    def handle_sticks_removed(self, sticks: List[Stick]):
        if sticks[0].camera_id != self.camera.id:
            return
        for stick in sticks:
            to_remove: StickWidget = None
            for sw in self.stick_widgets:
                if sw.stick.id == stick.id:
                    to_remove = sw
                    break
            self.stick_widgets.remove(to_remove)
            to_remove.setParentItem(None)
            if self.scene() is not None:
                self.scene().removeItem(to_remove)
            to_remove.deleteLater()
        self.update()

    def handle_sticks_added(self, sticks: List[Stick]):
        if len(sticks) == 0:
            return
        if sticks[0].camera_id != self.camera.id:
            return
        for stick in sticks:
            sw = StickWidget(stick, self.camera, self)
            sw.set_mode(self.stick_widget_mode)
            self.connect_stick_widget_signals(sw)
            self.stick_widgets.append(sw)
        self.update_stick_box()
        self.stick_widgets_out_of_sync.emit(self)
        self.update()

    def connect_stick_widget_signals(self, stick_widget: StickWidget):
        stick_widget.delete_clicked.connect(
            self.handle_stick_widget_delete_clicked)
        stick_widget.stick_changed.connect(self.handle_stick_widget_changed)
        stick_widget.link_initiated.connect(self.handle_stick_link_initiated)
        stick_widget.right_clicked.connect(
            self.handle_stick_widget_context_menu)

    def disconnect_stick_widget_signals(self, stick_widget: StickWidget):
        stick_widget.delete_clicked.disconnect(
            self.handle_stick_widget_delete_clicked)
        stick_widget.stick_changed.disconnect(self.handle_stick_widget_changed)
        stick_widget.link_initiated.disconnect(
            self.handle_stick_link_initiated)
        stick_widget.right_clicked.disconnect(
            self.handle_stick_widget_context_menu)

    def handle_stick_widget_delete_clicked(self, stick: Stick):
        self.camera.remove_stick(stick)

    def set_stick_widgets_mode(self, mode: StickMode):
        self.stick_widget_mode = mode
        for sw in self.stick_widgets:
            sw.set_mode(mode)
        self.set_stick_edit_mode(mode == StickMode.Edit)

    def handle_stick_widget_changed(self, stick_widget: StickWidget):
        self.camera.stick_changed.emit(stick_widget.stick)

    def handle_stick_changed(self, stick: Stick):
        if stick.camera_id != self.camera.id:
            return
        sw = next(
            filter(lambda _sw: _sw.stick.id == stick.id, self.stick_widgets))
        sw.adjust_line()
        sw.update_tooltip()

    def handle_stick_link_initiated(self, stick_widget: StickWidget):
        self.stick_link_requested.emit(stick_widget)

    def get_top_left(self) -> QPointF:
        return self.sceneBoundingRect().topLeft()

    def get_top_right(self) -> QPointF:
        return self.sceneBoundingRect().topRight()

    def highlight(self, color: Optional[QColor]):
        if color is None:
            self.highlight_animation.stop()
            self.highlight_rect.setVisible(False)
            return
        alpha = color.alpha()
        color.setAlpha(0)
        self.highlight_animation.setStartValue(color)
        self.highlight_animation.setEndValue(color)
        color.setAlpha(alpha)
        self.highlight_animation.setKeyValueAt(0.5, color)
        self.highlight_animation.setDuration(2000)
        self.highlight_animation.setLoopCount(-1)
        self.highlight_rect.setPen(QPen(color))
        self.highlight_rect.setVisible(True)
        self.highlight_animation.start()

    @pyqtProperty(QColor)
    def highlight_color(self) -> QColor:
        return self.current_highlight_color

    @highlight_color.setter
    def highlight_color(self, color: QColor):
        self.current_highlight_color = color

    def handle_highlight_color_changed(self, color: QColor):
        self.highlight_rect.setBrush(QBrush(color))
        self.update()

    def handle_stick_widget_context_menu(self, sender: Dict[str, StickWidget]):
        self.stick_context_menu.emit(sender['stick_widget'], self)

    def show_overlay_message(self, msg: Optional[str]):
        if msg is None:
            self.overlay_message.setVisible(False)
            self.blur_eff.setEnabled(False)
            return
        self.overlay_message.setText(msg)
        self.overlay_message.setPos(
            self.pixmap.boundingRect().center() -
            QPointF(0.5 * self.overlay_message.boundingRect().width(), 0.5 *
                    self.overlay_message.boundingRect().height()))
        self.overlay_message.setVisible(True)
        self.blur_eff.setEnabled(True)

    def show_status_message(self, msg: Optional[str]):
        if msg is None:
            self.control_widget.set_title_text(self.camera.folder.name)
        else:
            self.control_widget.set_title_text(msg)

    def update_stick_box(self):
        left = 9000
        right = 0
        top = 9000
        bottom = -1

        for stick in self.camera.sticks:
            left = min(left, min(stick.top[0], stick.bottom[0]))
            right = max(right, max(stick.top[0], stick.bottom[0]))
            top = min(top, min(stick.top[1], stick.bottom[1]))
            bottom = max(bottom, max(stick.top[1], stick.bottom[1]))
        left -= 100
        right += 100
        top -= 100
        bottom += 100
        self.stick_box.setRect(left, top, right - left, bottom - top)
        pen = QPen(QColor(0, 100, 200, 200))
        pen.setWidth(2)
        pen.setStyle(Qt.DashLine)
        self.stick_box.setPen(pen)

    def set_stick_edit_mode(self, is_edit: bool):
        if is_edit:
            self.update_stick_box()
            self.stick_box_start_pos = self.stick_box.pos()
            for sw in self.stick_widgets:
                sw.setParentItem(self.stick_box)
        else:
            offset = self.stick_box.pos() - self.stick_box_start_pos
            for sw in self.stick_widgets:
                stick = sw.stick
                stick.translate(np.array([int(offset.x()), int(offset.y())]))
                sw.setParentItem(self)
                sw.set_stick(stick)
            self.stick_box.setParentItem(None)
            self.stick_box = QGraphicsRectItem(self)
            self.stick_box.setFlag(QGraphicsItem.ItemIsMovable, True)
            self.stick_box.setVisible(False)
        self.stick_box.setVisible(is_edit)

    def keyPressEvent(self, event: QKeyEvent) -> None:
        pass

    def keyReleaseEvent(self, event: QKeyEvent) -> None:
        if event.key() in [Qt.Key_Right, Qt.Key_Tab, Qt.Key_Space]:
            self.control_widget.next_photo_btn.click_button(True)
        elif event.key() in [Qt.Key_Left]:
            self.control_widget.prev_photo_btn.click_button(True)
        elif event.key() == Qt.Key_S:
            self.enter_pressed.emit()
Example #14
0
    def __init__(self,  part_instance: ObjectInstance,
                        viewroot: GridRootItemT):
        """Summary

        Args:
            part_instance: ``ObjectInstance`` of the ``Part``
            viewroot: ``GridRootItem``
            parent: Default is ``None``
        """
        super(GridNucleicAcidPartItem, self).__init__(  part_instance,
                                                        viewroot)

        self._getActiveTool = viewroot.manager.activeToolGetter
        m_p = self._model_part
        self._controller = NucleicAcidPartItemController(self, m_p)
        self.scale_factor: float = self._RADIUS / m_p.radius()
        self.active_virtual_helix_item: GridVirtualHelixItem = None
        self.prexover_manager = PreXoverManager(self)
        self.hide()  # hide while until after attemptResize() to avoid flicker

        # set this to a token value
        self._rect: QRectF = QRectF(0., 0., 1000., 1000.)
        self.boundRectToModel()
        self.setPen(getNoPen())
        self.setRect(self._rect)

        self.setAcceptHoverEvents(True)

        # Cache of VHs that were active as of last call to activeGridChanged
        # If None, all grids will be redrawn and the cache will be filled.
        # Connect destructor. This is for removing a part from scenes.

        # initialize the NucleicAcidPartItem with an empty set of old coords
        self.setZValue(styles.ZPARTITEM)
        outline = QGraphicsRectItem(self)
        self.outline: QGraphicsRectItem = outline
        o_rect = self._configureOutline(outline)
        outline.setFlag(QGraphicsItem.ItemStacksBehindParent)
        outline.setZValue(styles.ZDESELECTOR)
        model_color = m_p.getColor()
        outline.setPen(getPenObj(model_color, _DEFAULT_WIDTH))

        GC_SIZE = 10
        self.grab_cornerTL: GrabCornerItem = GrabCornerItem(GC_SIZE,
                                                            model_color,
                                                            True,
                                                            self)
        self.grab_cornerTL.setTopLeft(o_rect.topLeft())
        self.grab_cornerBR: GrabCornerItem = GrabCornerItem(GC_SIZE,
                                                            model_color,
                                                            True,
                                                            self)
        self.grab_cornerBR.setBottomRight(o_rect.bottomRight())
        self.griditem: GridItem = GridItem(self, self._model_props['grid_type'])
        self.griditem.setZValue(1)
        self.grab_cornerTL.setZValue(2)
        self.grab_cornerBR.setZValue(2)

        # select upon creation
        for part in m_p.document().children():
            if part is m_p:
                part.setSelected(True)
            else:
                part.setSelected(False)
        self.show()
Example #15
0
class SelectAreaDialog(QDialog):

    finish_selecting_area = pyqtSignal(TrimmingData)

    def __init__(self):
        super().__init__()
        self.ui = Ui_SelectAreaDialog()
        self.ui.setupUi(self)

        self.width = 200
        self.height = 200
        self.h, self.w = None, None
        self.select_area = None
        self.original_image_scene = None
        self.size_flag = True
        self.select_area_label = None
        self.select_area_label_proxy = None
        self.start_position = None

        self.get_ng_sample_image_path()

        if self.h <= self.height and self.w <= self.width:
            self.size_flag = False

        if self.size_flag:
            self.show_select_area_at_default_position()
        else:
            self.ui.notation_label.setText('この画像サイズは十分小さいため, 画像全体でトレーニングを行います.'
                                           '\nこのままトレーニング開始ボタンを押してください.')
            pass

        self.ui.ok_button.clicked.connect(self.on_clicked_ok_button)
        self.ui.cancel_button.clicked.connect(self.on_clicked_cancel_button)

    def get_ng_sample_image_path(self):
        test_ng_path = str(Dataset.images_path(Dataset.Category.TEST_NG))
        test_ng_images = os.listdir(test_ng_path)
        test_ng_images = [
            img for img in test_ng_images
            if Path(img).suffix in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']
        ]
        if not test_ng_images:
            return
        original_image_path = os.path.join(test_ng_path, test_ng_images[0])
        original_image = cv2.imread(original_image_path)
        h, w, c = original_image.shape
        self.h, self.w = h, w
        original_image_shape = QSize(w + 2, h + 10)
        original_image_item = QGraphicsPixmapItem(QPixmap(original_image_path))
        original_image_item.setZValue(0)
        self.original_image_scene = QGraphicsScene()
        self.original_image_scene.addItem(original_image_item)
        self.ui.original_image_view.setScene(self.original_image_scene)
        self.ui.original_image_view.setBaseSize(original_image_shape)
        self.ui.original_image_view.setMaximumSize(original_image_shape)
        self.resize(self.w + 32, self.h + 72)

    def show_select_area_at_default_position(self):
        trimming_data = Project.latest_trimming_data()
        if trimming_data.position:
            self.start_position = QPoint(trimming_data.position[0],
                                         trimming_data.position[1])
            rect = QRectF(trimming_data.position[0], trimming_data.position[1],
                          self.width, self.height)
        else:
            self.start_position = QPoint((self.w - self.width) // 2,
                                         (self.h - self.height) // 2)
            rect = QRectF((self.w - self.width) // 2,
                          (self.h - self.height) // 2, self.width, self.height)
        self.select_area = QGraphicsRectItem(rect)
        self.select_area.setZValue(1)
        pen = QPen(QColor('#ffa00e'))
        pen.setWidth(4)
        pen.setJoinStyle(Qt.RoundJoin)
        self.select_area.setPen(pen)
        self.select_area.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.original_image_scene.addItem(self.select_area)
        self.select_area_label_proxy = QGraphicsProxyWidget(self.select_area)
        self.select_area_label = SelectAreaLabel()
        self.select_area_label.set_label()
        self.select_area_label_proxy.setWidget(self.select_area_label)
        self.select_area_label_proxy.setPos(
            self.select_area.boundingRect().left() + 2,
            self.select_area.boundingRect().bottom() -
            self.select_area_label.height() - 2)

    def on_clicked_ok_button(self):
        if not self.size_flag:
            trimming_data = TrimmingData(position=(0, 0),
                                         size=(self.w, self.h),
                                         needs_trimming=False)
            self.finish_selecting_area.emit(trimming_data)
            self.close()
        else:
            rel_position = self.select_area.pos()
            position = (self.start_position.x() + rel_position.x(),
                        self.start_position.y() + rel_position.y())
            if position[0] < 0 or position[
                    0] > self.w - self.width - 1 or position[
                        1] < 0 or position[1] > self.h - self.height - 1:
                print('Error: Please set area contained in the image.')
                self.ui.notation_label.setText('エラー: 切り取る領域は画像内に収まるようにしてください.')
            else:
                trimming_data = TrimmingData(position=position,
                                             size=(self.width, self.height),
                                             needs_trimming=True)
                self.finish_selecting_area.emit(trimming_data)
                self.close()

    def on_clicked_cancel_button(self):
        self.close()

    def closeEvent(self, QCloseEvent):
        self.close()