Beispiel #1
0
    def drawGrid(self):
        black_notes = [2,4,6,9,11]
        scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
        scale_bar.setPos(self.piano_width, 0)
        scale_bar.setBrush(QColor(100,100,100))
        clearpen = QPen(QColor(0,0,0,0))
        for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1):
            for j in range(self.notes_in_octave, 0, -1):
                scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
                scale_bar.setPos(self.piano_width, self.note_height * j + self.octave_height * (i - 1))
                scale_bar.setPen(clearpen)
                if j not in black_notes:
                    scale_bar.setBrush(QColor(120,120,120))
                else:
                    scale_bar.setBrush(QColor(100,100,100))

        measure_pen = QPen(QColor(0, 0, 0, 120), 3)
        half_measure_pen = QPen(QColor(0, 0, 0, 40), 2)
        line_pen = QPen(QColor(0, 0, 0, 40))
        for i in range(0, int(self.num_measures) + 1):
            measure = QGraphicsLineItem(0, 0, 0, self.piano_height + self.header_height - measure_pen.width(), self.header)
            measure.setPos(self.measure_width * i, 0.5 * measure_pen.width())
            measure.setPen(measure_pen)
            if i < self.num_measures:
                number = QGraphicsSimpleTextItem('%d' % (i + 1), self.header)
                number.setPos(self.measure_width * i + 5, 2)
                number.setBrush(Qt.white)
                for j in self.frange(0, self.time_sig[0]*self.grid_div/self.time_sig[1], 1.):
                    line = QGraphicsLineItem(0, 0, 0, self.piano_height, self.header)
                    line.setZValue(1.0)
                    line.setPos(self.measure_width * i + self.value_width * j, self.header_height)
                    if j == self.time_sig[0]*self.grid_div/self.time_sig[1] / 2.0:
                        line.setPen(half_measure_pen)
                    else:
                        line.setPen(line_pen)
Beispiel #2
0
class WayPoint:
    def __init__(self, **kwargs):
        super().__init__()
        self.location = MapPoint()
        self.__dict__.update(kwargs)

        self.pixmap = QGraphicsPixmapItem(
            QPixmap('HOME_DIR + /nparse/data/maps/waypoint.png'))
        self.pixmap.setOffset(-10, -20)

        self.line = QGraphicsLineItem(0.0, 0.0, self.location.x,
                                      self.location.y)
        self.line.setPen(QPen(Qt.green, 1, Qt.DashLine))
        self.line.setVisible(False)

        self.pixmap.setZValue(5)
        self.line.setZValue(4)

        self.pixmap.setPos(self.location.x, self.location.y)

    def update_(self, scale, location=None):
        self.pixmap.setScale(scale)
        if location:
            line = self.line.line()
            line.setP1(QPointF(location.x, location.y))
            self.line.setLine(line)

            pen = self.line.pen()
            pen.setWidth(1 / scale)
            self.line.setPen(pen)

            self.line.setVisible(True)
Beispiel #3
0
    def _constructTree(self, node, x, y, par_x, par_y, text, squared=True, depth=1, h_index=0):
        if squared:
            node_item = BinTreeSquareNodeGraphicsItem(text, depth=depth)
        else:
            node_item = BinTreeNodeGraphicsItem(text, depth=depth)
        
        self.nodeCount += 1
        
        node_item.setBrush(Qt.white)
        if node.data in self.regionPoints:
            node_item.setPen(Qt.green)

        self.addItem(node_item)
        node_item.moveBy(x, y)
        dx, dy = self.width() / (2 ** (depth + 2)), 2 * self.nodeWidth

        text_item = QGraphicsTextItem(text)
        text_item.moveBy(x-self.nodeWidth/4, y-self.nodeWidth/4)
        self.addItem(text_item)

        edge = QGraphicsLineItem(x, y, par_x, par_y)
        edge.setZValue(-1)
        self.addItem(edge)

        if node.left:
            self._constructTree(node.left, x-dx, y+dy, x, y, str(node.left.data), not squared, depth+1, 2*h_index)
        if node.right:
            self._constructTree(node.right, x+dx, y+dy, x, y, str(node.right.data), not squared, depth+1, 2*h_index+1)
Beispiel #4
0
    def drawGrid(self):
        black_notes = [2,4,6,9,11]
        scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
        scale_bar.setPos(self.piano_width, 0)
        scale_bar.setBrush(QColor(100,100,100))
        clearpen = QPen(QColor(0,0,0,0))
        for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1):
            for j in range(self.notes_in_octave, 0, -1):
                scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
                scale_bar.setPos(self.piano_width, self.note_height * j + self.octave_height * (i - 1))
                scale_bar.setPen(clearpen)
                if j not in black_notes:
                    scale_bar.setBrush(QColor(120,120,120))
                else:
                    scale_bar.setBrush(QColor(100,100,100))

        measure_pen = QPen(QColor(0, 0, 0, 120), 3)
        half_measure_pen = QPen(QColor(0, 0, 0, 40), 2)
        line_pen = QPen(QColor(0, 0, 0, 40))
        for i in range(0, int(self.num_measures) + 1):
            measure = QGraphicsLineItem(0, 0, 0, self.piano_height + self.header_height - measure_pen.width(), self.header)
            measure.setPos(self.measure_width * i, 0.5 * measure_pen.width())
            measure.setPen(measure_pen)
            if i < self.num_measures:
                number = QGraphicsSimpleTextItem('%d' % (i + 1), self.header)
                number.setPos(self.measure_width * i + 5, 2)
                number.setBrush(Qt.white)
                for j in self.frange(0, self.time_sig[0]*self.grid_div/self.time_sig[1], 1.):
                    line = QGraphicsLineItem(0, 0, 0, self.piano_height, self.header)
                    line.setZValue(1.0)
                    line.setPos(self.measure_width * i + self.value_width * j, self.header_height)
                    if j == self.time_sig[0]*self.grid_div/self.time_sig[1] / 2.0:
                        line.setPen(half_measure_pen)
                    else:
                        line.setPen(line_pen)
 def create_axis(self):
     bounding_end = abs(self.dimension_analysis.bounding_rect[3])
     bounding_start = abs(self.dimension_analysis.bounding_rect[2])
     pen = QPen()
     pen.setWidthF(0.5)
     # horizontal line
     self.graph_zero[0] = self.position[0] + self.margin - self.line_extend
     self.graph_zero[1] = self.position[1] + bounding_start * self.height_multiplier + self.margin
     self.graph_end[0] = self.graph_zero[0] + self.content_width + self.line_extend
     self.graph_end[1] = self.graph_zero[1]
     line_item_horizontal = QGraphicsLineItem(self.graph_zero[0], self.graph_zero[1], self.graph_end[0], self.graph_end[1])
     line_item_horizontal.setPen(pen)
     self.addToGroup(line_item_horizontal)
     center = (self.graph_zero[0] + self.line_extend), self.graph_zero[1]
     y_top = center[1] - (bounding_start*self.height_multiplier)
     y_bottom = center[1]+(bounding_end*self.height_multiplier)
     line_item_vertical = QGraphicsLineItem(center[0], y_top, center[0], y_bottom)
     line_item_vertical.setPen(pen)
     self.addToGroup(line_item_vertical)
     pen_thin = QPen()
     pen_thin.setWidthF(0.2)
     start_graph = center[1] - 10
     while start_graph > center[1] - bounding_start * self.height_multiplier:
         line_item_horizontal = QGraphicsLineItem(self.graph_zero[0], start_graph, self.graph_end[0], start_graph)
         line_item_horizontal.setPen(pen_thin)
         line_item_horizontal.setZValue(-0.5)
         self.addToGroup(line_item_horizontal)
         start_graph -= 10
     start_graph = center[1] + 10
     while start_graph < center[1] + bounding_end * self.height_multiplier:
         line_item_horizontal = QGraphicsLineItem(self.graph_zero[0], start_graph, self.graph_end[0], start_graph)
         line_item_horizontal.setPen(pen_thin)
         line_item_horizontal.setZValue(-0.5)
         self.addToGroup(line_item_horizontal)
         start_graph += 10
Beispiel #6
0
    def refresh(self):
        if not self.items():
            return
        super().refresh()
        
        for i in self.items():
            if isinstance(i, QGraphicsLineItem):
                self.removeItem(i)


        hull = self.hull_method(self.point_model.points)
        pts = hull + [hull[0]]


        point_items = list(filter(lambda i: isinstance(i, PointGraphicsItem), self.items()))
        for p in point_items:
            p.setBrush(Qt.blue)

        min_point = min(point_items, key=lambda p:p.x())
        min_point.setBrush(Qt.green)

        for i in range(len(pts)-1):
            dx, dy = pts[i+1].x - pts[i].x, pts[i+1].y - pts[i].y
            line = QGraphicsLineItem(pts[i+1].x, pts[i+1].y, pts[i+1].x+dx, pts[i+1].y+dy)
            pen = QPen()
            pen.setStyle(Qt.DashLine)
            line.setPen(pen)
            line.setZValue(-2)
            self.addItem(line)
            h_line = QGraphicsLineItem(pts[i].x, pts[i].y, pts[i+1].x, pts[i+1].y)
            h_line.setPen(Qt.red)
            h_line.setZValue(-1)


        self.enumeratePoints()
Beispiel #7
0
 def add_graphics_line_item(self, line):
     line_item = QGraphicsLineItem(line)
     line_item.setPen(self.pen)
     line_item.setZValue(-1)
     scene = self.model.gui.scene
     scene.addItem(line_item)
     pair = (line, line_item)
     self.line_pairs.append(pair)
     return pair
Beispiel #8
0
 def drawLrLine(self):
     leftmost = list(filter(lambda i: i.point == self.algorithm.stageResults[0][0], self.items()))[0]
     rightmost = list(filter(lambda i: i.point == self.algorithm.stageResults[0][1], self.items()))[0]
     leftmost.setBrush(Qt.red)
     rightmost.setBrush(Qt.red)
     
     lr_line = QGraphicsLineItem(leftmost.x(), leftmost.y(), rightmost.x(), rightmost.y())
     lr_line.setPen(Qt.red)
     lr_line.setZValue(-1)
     self.addItem(lr_line)
Beispiel #9
0
 def drawLinesFromOriginToPoints(self):
     points = self.algorithm.stageResults[1]
     for p in points:
         line = QGraphicsLineItem(self.originItem.x(), self.originItem.y(),
                                  p.x, p.y)
         pen = QPen()
         pen.setStyle(Qt.DashLine)
         line.setPen(pen)
         line.setZValue(-1)
         self.addItem(line)
Beispiel #10
0
    def updateCurve(self, name, color=Qt.black):
        # if name in self.curveObjs:
        #     curveitem = self.curveObjs[name]
        # else:
        #     curveitem = QGraphicsPathItem()
        #     self.scene.addItem(curveitem)
        # # path=curveitem.path()
        # path = QPainterPath()
        #
        # pointItems = self.curvePointObjs[name]
        # if len(pointItems) > 0:
        #     path.moveTo(pointItems[0].pos())
        # for pointitem in pointItems[1:]:
        #     path.lineTo(pointitem.pos())
        # curveitem.setPath(path)
        # curveitem.update(curveitem.boundingRect())
        # curveitem.prepareGeometryChange()
        # self.scene.update()
        # self.viewport().repaint()
        # self.viewport().update()

        if not isinstance(name, str):
            return
        if name not in self.pointObjs:
            return

        lastitems = []
        if name in self.curveObjs:
            lastitems = self.curveObjs[name]
            if not isinstance(lastitems, list):
                lastitems = []

        if name in self.pointObjs:
            pointItems = self.pointObjs[name]
        else:
            pointItems = []
        points = []
        for ptitem in pointItems:
            points.append(ptitem.pos())

        self.curveObjs[name] = []
        if len(points) > 1:
            for i in range(1, len(points)):
                l = QGraphicsLineItem(points[i - 1].x(), points[i - 1].y(),
                                      points[i].x(), points[i].y())
                l.setPen(color)
                l.setZValue(10)
                # l.setFlag(QGraphicsItem.ItemIsSelectable)
                self.curveObjs[name].append(l)
                self.scene.addItem(l)

        for line in lastitems:
            self.scene.removeItem(line)

        self.updateCurvePoints(name)
Beispiel #11
0
    def drawHlrLines(self):
        hlr = self.algorithm.stageResults[1]
        for h, l, r in hlr:
            pen = QPen()
            pen.setStyle(Qt.PenStyle.DashLine)

            lh_line = QGraphicsLineItem(l.x, l.y, h.x, h.y)
            lh_line.setPen(pen)
            lh_line.setZValue(-1)
            rh_line = QGraphicsLineItem(r.x, r.y, h.x, h.y)
            rh_line.setPen(pen)
            rh_line.setZValue(-1)
            self.addItem(lh_line)
            self.addItem(rh_line)
Beispiel #12
0
 def createArrow(self):
     rad = self._RADIUS
     pen = QPen()
     pen.setWidth(3)
     color = QColor(Qt.blue)
     color.setAlphaF(0.25)
     pen.setBrush(color)
     if self._virtual_helix.isEvenParity():
         arrow = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
     else:
         arrow = QGraphicsLineItem(0, rad, rad, rad, self)
     arrow.setTransformOriginPoint(rad, rad)
     arrow.setZValue(400)
     arrow.setPen(pen)
     self.arrow = arrow
     self.arrow.hide()
    def on_actItem_Line_triggered(self):  # 添加直线
        item = QGraphicsLineItem(-100, 0, 100, 0)  # x,y 为左上角的图元局部坐标,图元中心点为0,0
        item.setFlags(QGraphicsItem.ItemIsMovable
                      | QGraphicsItem.ItemIsSelectable
                      | QGraphicsItem.ItemIsFocusable)

        pen = QPen(Qt.red)
        pen.setWidth(3)
        item.setPen(pen)
        self.view.frontZ = self.view.frontZ + 1
        item.setZValue(self.view.frontZ)
        item.setPos(-50 + (QtCore.qrand() % 100), -50 + (QtCore.qrand() % 100))

        self.view.seqNum = self.view.seqNum + 1
        item.setData(self.view.ItemId, self.view.seqNum)  # //自定义数据,ItemId键
        item.setData(self.view.ItemDesciption, "直线")

        self.scene.addItem(item)
        self.scene.clearSelection()
        item.setSelected(True)
Beispiel #14
0
    def drawEdges(self):
        points = self.algorithm.stageResults[1]
        table = self.algorithm.stageResults[2]
        for row in table:
            p1, p2, p3 = points[row[0][0] - 1], points[row[0][1] -
                                                       1], points[row[0][2] -
                                                                  1]
            line1 = QGraphicsLineItem(*p1.coords, *p2.coords)
            line2 = QGraphicsLineItem(*p2.coords, *p3.coords)
            line1.setZValue(-2)
            line2.setZValue(-2)

            if row[1]:
                line1.setPen(Qt.green)
                line2.setPen(Qt.green)
            else:
                line1.setPen(Qt.red)
                line2.setPen(Qt.red)

            self.addItem(line1)
            self.addItem(line2)
Beispiel #15
0
    def _constructTree(self, node, x, y, par_x, par_y, text, depth=1, h_index=0):
        node_item = BinTreeNodeGraphicsItem(text, node.data, depth)
        node_item.setBrush(Qt.white)
        self.addItem(node_item)
        node_item.moveBy(x, y)
        dx, dy = self.width() / (2 ** (depth + 2)), 4 * self.rad

        text_item = QGraphicsTextItem()
        text_item.setHtml(text+"</sub>")
        text_item.moveBy(x-self.rad/2, y-self.rad/2)
        self.addItem(text_item)

        edge = QGraphicsLineItem(x, y, par_x, par_y)
        edge.setZValue(-1)
        self.addItem(edge)

        if node.left:
            new_text = text + "1"
            self._constructTree(node.left, x-dx, y+dy, x, y, new_text, depth+1, 2*h_index)
        if node.right:
            new_text = text + "2"
            self._constructTree(node.right, x+dx, y+dy, x, y, new_text, depth+1, 2*h_index+1)            
Beispiel #16
0
    def updateGrid(self):
        for line in self.gridObjs:
            self.scene.removeItem(line)

        if self.gridxpos and self.gridypos:
            clr = QColor(self.proj.gridColor)
            clr.setAlphaF(self.proj.gridOpacity)
            for x in self.gridxpos:
                line = QGraphicsLineItem(x, self.gridypos[0], x,
                                         self.gridypos[-1])
                line.setZValue(5)
                line.setPen(
                    QPen(clr, self.proj.gridLineWidth, self.proj.gridLineType))
                self.gridObjs.append(line)
                self.scene.addItem(line)
            for y in self.gridypos:
                line = QGraphicsLineItem(self.gridxpos[0], y,
                                         self.gridxpos[-1], y)
                line.setZValue(5)
                line.setPen(
                    QPen(clr, self.proj.gridLineWidth, self.proj.gridLineType))
                self.gridObjs.append(line)
                self.scene.addItem(line)
Beispiel #17
0
    def makePartition(self):
        medians = self.algorithm.stageResults[1].to_list()
        directions = self.algorithm.stageResults[2]

        for i in range(len(medians)):
            point = medians[i]
            vertical = directions[i]
            line = None

            if vertical:
                lowermostY, uppermostY = self._boundingHorizontalLinesYCoords(*point.data.coords)
                x = point.data.x
                line = QGraphicsLineItem(
                    QLineF(
                        QPointF(x, lowermostY),
                        QPointF(x, uppermostY)
                    )
                )
                line.setPen(Qt.red)
                line.setZValue(-1)
                self.vLinesXCoords.append(x)
                self.vLinesYRanges.append([lowermostY, uppermostY])
            else:
                leftmostX, rightmostX = self._boundingVerticalLinesXCoords(*point.data.coords)
                y = point.data.y
                line = QGraphicsLineItem( 
                    QLineF(
                        QPointF(leftmostX, y),
                        QPointF(rightmostX, y)
                    )
                )
                line.setPen(Qt.green)
                line.setZValue(-2)
                self.hLinesYCoords.append(y)
                self.hLinesXRanges.append([leftmostX, rightmostX])
        
            self.addItem(line)
 def createArrows(self):
     rad = self._RADIUS
     pen1 = self._pen1
     pen2 = self._pen2
     pen1.setWidth(3)
     pen2.setWidth(3)
     pen1.setBrush(Qt.gray)
     pen2.setBrush(Qt.lightGray)
     if self._virtual_helix.isEvenParity():
         arrow1 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
         arrow2 = QGraphicsLineItem(0, rad, rad, rad, self)
     else:
         arrow1 = QGraphicsLineItem(0, rad, rad, rad, self)
         arrow2 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
     arrow1.setTransformOriginPoint(rad, rad)
     arrow2.setTransformOriginPoint(rad, rad)
     arrow1.setZValue(400)
     arrow2.setZValue(400)
     arrow1.setPen(pen1)
     arrow2.setPen(pen2)
     self.arrow1 = arrow1
     self.arrow2 = arrow2
     self.arrow1.hide()
     self.arrow2.hide()
Beispiel #19
0
 def createArrows(self):
     rad = _RADIUS
     pen1 = self._pen1
     pen2 = self._pen2
     pen1.setWidth(3)
     pen2.setWidth(3)
     pen1.setBrush(Qt.gray)
     pen2.setBrush(Qt.lightGray)
     if self._virtual_helix.isEvenParity():
         arrow1 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
         arrow2 = QGraphicsLineItem(0, rad, rad, rad, self)
     else:
         arrow1 = QGraphicsLineItem(0, rad, rad, rad, self)
         arrow2 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
     arrow1.setTransformOriginPoint(rad, rad)
     arrow2.setTransformOriginPoint(rad, rad)
     arrow1.setZValue(400)
     arrow2.setZValue(400)
     arrow1.setPen(pen1)
     arrow2.setPen(pen2)
     self.arrow1 = arrow1
     self.arrow2 = arrow2
     self.arrow1.hide()
     self.arrow2.hide()
Beispiel #20
0
class barChartView(QChartView):
    def __init__(self, xAxis=[], *args, **kwargs):
        super(barChartView, self).__init__(*args, **kwargs)
        self.initChart(xAxis)

        # line 宽度需要调整
        self.lineItem = QGraphicsLineItem(self._chart)
        pen = QPen(Qt.gray)
        self.lineItem.setPen(pen)
        self.lineItem.setZValue(998)
        self.lineItem.hide()
        self.cal()

    # 一些固定计算,减少mouseMoveEvent中的计算量
    def cal(self):
        # 提示widget
        self.toolTipWidget = GraphicsProxyWidget(self._chart)
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.category_len = len(axisX.categories())
        self.min_x, self.max_x = -0.5, self.category_len - 0.5
        self.min_y, self.max_y = axisY.min(), axisY.max()
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))

    def setCat(self, data):
        self.categories = data

    #初始化
    def initChart(self, xAxis):
        self._chart = QChart()
        # 调整边距
        self._chart.layout().setContentsMargins(0, 0, 0, 0)  # 外界
        self._chart.setMargins(QMargins(3, 0, 3, 0))  # 内界
        self._chart.setBackgroundRoundness(0)
        self._chart.setBackgroundVisible(False)
        # 设置主题
        self._chart.setTheme(QChart.ChartThemeBlueIcy)
        # 抗锯齿
        self.setRenderHint(QPainter.Antialiasing)
        # 开启动画效果
        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        self.categories = xAxis
        self._series = QBarSeries(self._chart)
        self._chart.addSeries(self._series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        self._axis_x = QBarCategoryAxis(self._chart)
        self._axis_x.append(self.categories)
        self._axis_y = QValueAxis(self._chart)
        self._axis_y.setTitleText("任务数")
        self._axis_y.setRange(0, 10)
        self._chart.setAxisX(self._axis_x, self._series)
        self._chart.setAxisY(self._axis_y, self._series)
        # chart的图例
        legend = self._chart.legend()
        legend.setVisible(True)

        self.setChart(self._chart)

    def mouseMoveEvent(self, event):
        super(barChartView, self).mouseMoveEvent(event)
        pos = event.pos()
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(pos).x()
        y = self._chart.mapToValue(pos).y()
        index = round(x)
        # 得到在坐标系中的所有bar的类型和点
        serie = self._chart.series()[0]
        bars = [
            (bar, bar.at(index)) for bar in serie.barSets()
            if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y
        ]
        # print(bars)
        if bars:
            right_top = self._chart.mapToPosition(
                QPointF(self.max_x, self.max_y))
            # 等分距离比例
            step_x = round(
                (right_top.x() - self.point_top.x()) / self.category_len)
            posx = self._chart.mapToPosition(QPointF(x, self.min_y))
            self.lineItem.setLine(posx.x(), self.point_top.y(), posx.x(),
                                  posx.y())
            self.lineItem.show()
            try:
                title = self.categories[index]
            except:
                title = ""
            t_width = self.toolTipWidget.width()
            t_height = self.toolTipWidget.height()
            # 如果鼠标位置离右侧的距离小于tip宽度
            x = pos.x() - t_width if self.width() - \
                pos.x() - 20 < t_width else pos.x()
            # 如果鼠标位置离底部的高度小于tip高度
            y = pos.y() - t_height if self.height() - \
                pos.y() - 20 < t_height else pos.y()
            self.toolTipWidget.show(title, bars, QPoint(x, y))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()
Beispiel #21
0
class ChartView(QChartView):

    def __init__(self, *args, **kwargs):
        super(ChartView, self).__init__(*args, **kwargs)
        self.resize(800, 600)
        self.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
        self.initChart()

        self.toolTipWidget = GraphicsProxyWidget(self._chart)

        # line
        self.lineItem = QGraphicsLineItem(self._chart)
        self.lineItem.setZValue(998)
        self.lineItem.hide()
        
        # 一些固定计算,减少mouseMoveEvent中的计算量
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.min_x, self.max_x = axisX.min(), axisX.max()
        self.min_y, self.max_y = axisY.min(), axisY.max()
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(QPointF(self.min_x, self.max_y))
        # 坐标原点坐标
        self.point_bottom = self._chart.mapToPosition(QPointF(self.min_x, self.min_y))
        self.step_x = (self.max_x - self.min_x) / (axisX.tickCount() - 1)
#         self.step_y = (self.max_y - self.min_y) / (axisY.tickCount() - 1)

    def mouseMoveEvent(self, event):
        super(ChartView, self).mouseMoveEvent(event)
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(event.pos()).x()
        y = self._chart.mapToValue(event.pos()).y()
        index = round((x - self.min_x) / self.step_x)
        pos_x = self._chart.mapToPosition(
            QPointF(index * self.step_x + self.min_x, self.min_y))
#         print(x, pos_x, index, index * self.step_x + self.min_x)
        # 得到在坐标系中的所有series的类型和点
        points = [(serie, serie.at(index))
                  for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
        if points:
            # 永远在轴上的黑线条
            self.lineItem.setLine(pos_x.x(), self.point_top.y(),
                                  pos_x.x(), self.point_bottom.y())
            self.lineItem.show()
            self.toolTipWidget.show("", points, event.pos() + QPoint(20, 20))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()

    def onSeriesHoverd(self, point, state):
        if state:
            try:
                name = self.sender().name()
            except:
                name = ""
            QToolTip.showText(QCursor.pos(), "%s\nx: %s\ny: %s" % 
                              (name, point.x(), point.y()))

    def initChart(self):
        self._chart = QChart(title="Line Chart")
        self._chart.setAcceptHoverEvents(True)
        dataTable = [
            [120, 132, 101, 134, 90, 230, 210],
            [220, 182, 191, 234, 290, 330, 310],
            [150, 232, 201, 154, 190, 330, 410],
            [320, 332, 301, 334, 390, 330, 320],
            [820, 932, 901, 934, 1290, 1330, 1320]
        ]
        for i, data_list in enumerate(dataTable):
            series = QLineSeries(self._chart)
            for j, v in enumerate(data_list):
                series.append(j, v)
            series.setName("Series " + str(i))
            series.setPointsVisible(True)  # 显示原点
            series.hovered.connect(self.onSeriesHoverd)
            self._chart.addSeries(series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        self._chart.axisX().setTickCount(7)  # x轴设置7个刻度
        self._chart.axisY().setTickCount(7)  # y轴设置7个刻度
        self._chart.axisY().setRange(0, 1500)  # 设置y轴范围
        self.setChart(self._chart)
Beispiel #22
0
 def _AddBackLineItem(self, x1, y1, x2, y2, pen):
     line = QGraphicsLineItem(x1, y1, x2, y2)
     line.setPen(pen)
     line.setZValue(self.m_LineZValue)
     self.addItem(line)
Beispiel #23
0
class ChartView(QChartView):
    def __init__(self, *args, **kwargs):
        super(ChartView, self).__init__(*args, **kwargs)
        self.resize(800, 600)
        self.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
        # 自定义x轴label
        self.category = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
        self.initChart()

        # 提示widget
        self.toolTipWidget = GraphicsProxyWidget(self._chart)

        # line
        self.lineItem = QGraphicsLineItem(self._chart)
        pen = QPen(Qt.gray)
        pen.setWidth(1)

        self.lineItem.setPen(pen)
        self.lineItem.setZValue(996)
        self.lineItem.hide()

        self.lineItemX = QGraphicsLineItem(self._chart)
        penx = QPen(Qt.gray)
        penx.setWidth(1)
        self.lineItemX.setPen(penx)
        self.lineItemX.setZValue(997)
        self.lineItemX.hide()

        # 一些固定计算,减少mouseMoveEvent中的计算量
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.min_x, self.max_x = axisX.min(), axisX.max()
        self.min_y, self.max_y = axisY.min(), axisY.max()

    def resizeEvent(self, event):
        super(ChartView, self).resizeEvent(event)
        # 当窗口大小改变时需要重新计算
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))
        # 坐标右上点
        self.point_right_top = self._chart.mapToPosition(
            QPointF(self.max_x, self.max_y))
        # 坐标右下点
        self.point_right_bottom = self._chart.mapToPosition(
            QPointF(self.max_x, self.min_y))
        # 坐标原点坐标
        self.point_bottom = self._chart.mapToPosition(
            QPointF(self.min_x, self.min_y))
        self.step_x = (self.max_x - self.min_x) / \
            (self._chart.axisX().tickCount() - 1)

    def mouseMoveEvent(self, event):
        super(ChartView, self).mouseMoveEvent(event)
        pos = event.pos()
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(pos).x()
        y = self._chart.mapToValue(pos).y()

        # 根据间隔来确定鼠标当前所在的索引
        index = round((x - self.min_x) / self.step_x)
        # 得到在坐标系中的所有正常显示的series的类型和点
        points = [
            (serie, serie.at(index)) for serie in self._chart.series()
            if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y
        ]
        if points:
            # 根据鼠标的点获取对应曲线的坐标点,
            # pos_x = self._chart.mapToPosition(
            #     QPointF(index * self.step_x + self.min_x, self.min_y))
            # 设置鼠标的垂直线
            self.lineItem.setLine(pos.x(), self.point_top.y(), pos.x(),
                                  self.point_bottom.y())
            self.lineItem.show()

            # 设置鼠标的水平线
            self.lineItemX.setLine(self.point_top.x(), pos.y(),
                                   self.point_right_top.x(), pos.y())
            self.lineItemX.show()

            try:
                title = self.category[index]
            except:
                title = ""
            t_width = self.toolTipWidget.width()
            t_height = self.toolTipWidget.height()
            # 如果鼠标位置离右侧的距离小于tip宽度
            x = pos.x() - t_width if self.width() - \
                pos.x() - 20 < t_width else pos.x()
            # 如果鼠标位置离底部的高度小于tip高度
            y = pos.y() - t_height if self.height() - \
                pos.y() - 20 < t_height else pos.y()
            self.toolTipWidget.show(title, points, QPoint(x, y))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()
            self.lineItemX.hide()

    def handleMarkerClicked(self):
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        visible = not marker.series().isVisible()
        # 隐藏或显示series
        marker.series().setVisible(visible)
        marker.setVisible(True)  # 要保证marker一直显示
        # 透明度
        alpha = 1.0 if visible else 0.4
        # 设置label的透明度
        brush = marker.labelBrush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setLabelBrush(brush)
        # 设置marker的透明度
        brush = marker.brush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setBrush(brush)
        # 设置画笔透明度
        pen = marker.pen()
        color = pen.color()
        color.setAlphaF(alpha)
        pen.setColor(color)
        marker.setPen(pen)

    def handleMarkerHovered(self, status):
        # 设置series的画笔宽度
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        series = marker.series()
        if not series:
            return
        pen = series.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if status else -1))
        series.setPen(pen)

    def handleSeriesHoverd(self, point, state):
        # 设置series的画笔宽度
        series = self.sender()  # 信号发送者
        pen = series.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if state else -1))
        series.setPen(pen)

    def initChart(self):
        self._chart = QChart(title="折线图堆叠")
        self._chart.setAcceptHoverEvents(True)
        # Series动画
        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        dataTable = [["邮件营销", [120, 132, 101, 134, 90, 230, 210]],
                     ["联盟广告", [220, 182, 191, 234, 290, 330, 310]],
                     ["视频广告", [150, 232, 201, 154, 190, 330, 410]],
                     ["直接访问", [320, 332, 301, 334, 390, 330, 320]],
                     ["搜索引擎", [820, 932, 901, 934, 1290, 1330, 1320]]]
        for series_name, data_list in dataTable:
            series = QLineSeries(self._chart)
            for j, v in enumerate(data_list):
                series.append(j, v)
            series.setName(series_name)
            series.setPointsVisible(True)  # 显示圆点
            series.hovered.connect(self.handleSeriesHoverd)  # 鼠标悬停
            self._chart.addSeries(series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        axisX = self._chart.axisX()  # x轴
        axisX.setTickCount(7)  # x轴设置7个刻度
        axisX.setGridLineVisible(False)  # 隐藏从x轴往上的线条
        axisY = self._chart.axisY()
        axisY.setTickCount(7)  # y轴设置7个刻度
        axisY.setRange(0, 1500)  # 设置y轴范围
        # 自定义x轴
        axis_x = QCategoryAxis(
            self._chart,
            labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue)
        axis_x.setTickCount(7)
        axis_x.setGridLineVisible(False)
        min_x = axisX.min()
        max_x = axisX.max()
        step = (max_x - min_x) / (7 - 1)  # 7个tick
        for i in range(0, 7):
            axis_x.append(self.category[i], min_x + i * step)
        self._chart.setAxisX(axis_x, self._chart.series()[-1])
        # self._chart.addAxis(axis_x, Qt.AlignBottom)
        # chart的图例
        legend = self._chart.legend()
        # 设置图例由Series来决定样式
        legend.setMarkerShape(QLegend.MarkerShapeFromSeries)
        # 遍历图例上的标记并绑定信号
        for marker in legend.markers():
            # 点击事件
            marker.clicked.connect(self.handleMarkerClicked)
            # 鼠标悬停事件
            marker.hovered.connect(self.handleMarkerHovered)
        self.setChart(self._chart)
Beispiel #24
0
class PianoRoll(QGraphicsScene):
    '''the piano roll'''

    midievent = pyqtSignal(list)
    measureupdate = pyqtSignal(int)
    modeupdate = pyqtSignal(str)

    def __init__(self, time_sig = '4/4', num_measures = 4, quantize_val = '1/8'):
        QGraphicsScene.__init__(self)
        self.setBackgroundBrush(QColor(50, 50, 50))
        self.mousePos = QPointF()

        self.notes = []
        self.selected_notes = []
        self.piano_keys = []

        self.marquee_select = False
        self.insert_mode = False
        self.velocity_mode = False
        self.place_ghost = False
        self.ghost_note = None
        self.default_ghost_vel = 100
        self.ghost_vel = self.default_ghost_vel

        ## dimensions
        self.padding = 2

        ## piano dimensions
        self.note_height = 10
        self.start_octave = -2
        self.end_octave = 8
        self.notes_in_octave = 12
        self.total_notes = (self.end_octave - self.start_octave) \
                * self.notes_in_octave + 1
        self.piano_height = self.note_height * self.total_notes
        self.octave_height = self.notes_in_octave * self.note_height

        self.piano_width = 34

        ## height
        self.header_height = 20
        self.total_height = self.piano_height - self.note_height + self.header_height
        #not sure why note_height is subtracted

        ## width
        self.full_note_width = 250 # i.e. a 4/4 note
        self.snap_value = None
        self.quantize_val = quantize_val

        ### dummy vars that will be changed
        self.time_sig = 0
        self.measure_width = 0
        self.num_measures = 0
        self.max_note_length = 0
        self.grid_width = 0
        self.value_width = 0
        self.grid_div = 0
        self.piano = None
        self.header = None
        self.play_head = None

        self.setTimeSig(time_sig)
        self.setMeasures(num_measures)
        self.setGridDiv()
        self.default_length = 1. / self.grid_div


    # -------------------------------------------------------------------------
    # Callbacks

    def movePlayHead(self, transport_info):
        # TODO: need conversion between frames and PPQ
        x = 105. # works for 120bpm
        total_duration = self.time_sig[0] * self.num_measures * x
        pos = transport_info['frame'] / x
        frac = (pos % total_duration) / total_duration
        self.play_head.setPos(QPointF(frac * self.grid_width, 0))

    def setTimeSig(self, time_sig):
        try:
           new_time_sig = list(map(float, time_sig.split('/')))
           if len(new_time_sig)==2:
               self.time_sig = new_time_sig

               self.measure_width = self.full_note_width * self.time_sig[0]/self.time_sig[1]
               self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
               self.grid_width = self.measure_width * self.num_measures
               self.setGridDiv()
        except ValueError:
            pass

    def setMeasures(self, measures):
        try:
            self.num_measures = float(measures)
            self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
            self.grid_width = self.measure_width * self.num_measures
            self.refreshScene()
        except:
            pass

    def setDefaultLength(self, length):
        try:
            v = list(map(float, length.split('/')))
            if len(v) < 3:
                self.default_length = \
                        v[0] if len(v)==1 else \
                        v[0] / v[1]
                pos = self.enforce_bounds(self.mousePos)
                if self.insert_mode: self.makeGhostNote(pos.x(), pos.y())
        except ValueError:
            pass

    def setGridDiv(self, div=None):
        if not div: div = self.quantize_val
        try:
            val = list(map(int, div.split('/')))
            if len(val) < 3:
                self.quantize_val = div
                self.grid_div = val[0] if len(val)==1 else val[1]
                self.value_width = self.full_note_width / float(self.grid_div) if self.grid_div else None
                self.setQuantize(div)

                self.refreshScene()
        except ValueError:
            pass

    def setQuantize(self, value):
        try:
            val = list(map(float, value.split('/')))
            if len(val) == 1:
                self.quantize(val[0])
                self.quantize_val = value
            elif len(val) == 2:
                self.quantize(val[0] / val[1])
                self.quantize_val = value
        except ValueError:
            pass

    # -------------------------------------------------------------------------
    # Event Callbacks

    def keyPressEvent(self, event):
        QGraphicsScene.keyPressEvent(self, event)
        if event.key() == Qt.Key_F:
            if not self.insert_mode:
                self.velocity_mode = False
                self.insert_mode = True
                self.makeGhostNote(self.mousePos.x(), self.mousePos.y())
                self.modeupdate.emit('insert_mode')
            elif self.insert_mode:
                self.insert_mode = False
                if self.place_ghost: self.place_ghost = False
                self.removeItem(self.ghost_note)
                self.ghost_note = None
                self.modeupdate.emit('')
        elif event.key() == Qt.Key_D:
            if self.velocity_mode:
                self.velocity_mode = False
                self.modeupdate.emit('')
            else:
                if self.insert_mode:
                    self.removeItem(self.ghost_note)
                self.ghost_note = None
                self.insert_mode = False
                self.place_ghost = False
                self.velocity_mode = True
                self.modeupdate.emit('velocity_mode')
        elif event.key() == Qt.Key_A:
            if all((note.isSelected() for note in self.notes)):
                for note in self.notes:
                    note.setSelected(False)
                self.selected_notes = []
            else:
                for note in self.notes:
                    note.setSelected(True)
                self.selected_notes = self.notes[:]
        elif event.key() in (Qt.Key_Delete, Qt.Key_Backspace):
            self.notes = [note for note in self.notes if note not in self.selected_notes]
            for note in self.selected_notes:
                self.removeItem(note)
                self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
                del note
            self.selected_notes = []

    def mousePressEvent(self, event):
        QGraphicsScene.mousePressEvent(self, event)
        if not (any(key.pressed for key in self.piano_keys)
                or any(note.pressed for note in self.notes)):
            for note in self.selected_notes:
                note.setSelected(False)
            self.selected_notes = []

            if event.button() == Qt.LeftButton:
                if self.insert_mode:
                    self.place_ghost = True
                else:
                    self.marquee_select = True
                    self.marquee_rect = QRectF(event.scenePos().x(), event.scenePos().y(), 1, 1)
                    self.marquee = QGraphicsRectItem(self.marquee_rect)
                    self.marquee.setBrush(QColor(255, 255, 255, 100))
                    self.addItem(self.marquee)
        else:
            for s_note in self.notes:
                if s_note.pressed and s_note in self.selected_notes:
                    break
                elif s_note.pressed and s_note not in self.selected_notes:
                    for note in self.selected_notes:
                        note.setSelected(False)
                    self.selected_notes = [s_note]
                    break
            for note in self.selected_notes:
                if not self.velocity_mode:
                    note.mousePressEvent(event)

    def mouseMoveEvent(self, event):
        QGraphicsScene.mouseMoveEvent(self, event)
        self.mousePos = event.scenePos()
        if not (any((key.pressed for key in self.piano_keys))):
            m_pos = event.scenePos()
            if self.insert_mode and self.place_ghost: #placing a note
                m_width = self.ghost_rect.x() + self.ghost_rect_orig_width
                if m_pos.x() > m_width:
                    m_new_x = self.snap(m_pos.x())
                    self.ghost_rect.setRight(m_new_x)
                    self.ghost_note.setRect(self.ghost_rect)
                #self.adjust_note_vel(event)
            else:
                m_pos = self.enforce_bounds(m_pos)

                if self.insert_mode: #ghostnote follows mouse around
                    (m_new_x, m_new_y) = self.snap(m_pos.x(), m_pos.y())
                    self.ghost_rect.moveTo(m_new_x, m_new_y)
                    try:
                        self.ghost_note.setRect(self.ghost_rect)
                    except RuntimeError:
                        self.ghost_note = None
                        self.makeGhostNote(m_new_x, m_new_y)

                elif self.marquee_select:
                    marquee_orig_pos = event.buttonDownScenePos(Qt.LeftButton)
                    if marquee_orig_pos.x() < m_pos.x() and marquee_orig_pos.y() < m_pos.y():
                        self.marquee_rect.setBottomRight(m_pos)
                    elif marquee_orig_pos.x() < m_pos.x() and marquee_orig_pos.y() > m_pos.y():
                        self.marquee_rect.setTopRight(m_pos)
                    elif marquee_orig_pos.x() > m_pos.x() and marquee_orig_pos.y() < m_pos.y():
                        self.marquee_rect.setBottomLeft(m_pos)
                    elif marquee_orig_pos.x() > m_pos.x() and marquee_orig_pos.y() > m_pos.y():
                        self.marquee_rect.setTopLeft(m_pos)
                    self.marquee.setRect(self.marquee_rect)
                    self.selected_notes = []
                    for item in self.collidingItems(self.marquee):
                        if item in self.notes:
                            self.selected_notes.append(item)

                    for note in self.notes:
                        if note in self.selected_notes: note.setSelected(True)
                        else: note.setSelected(False)

                elif self.velocity_mode:
                    if Qt.LeftButton == event.buttons():
                        for note in self.selected_notes:
                            note.updateVelocity(event)

                elif not self.marquee_select: #move selected
                    if Qt.LeftButton == event.buttons():
                        x = y = False
                        if any(note.back.stretch for note in self.selected_notes):
                            x = True
                        elif any(note.front.stretch for note in self.selected_notes):
                            y = True
                        for note in self.selected_notes:
                            note.back.stretch = x
                            note.front.stretch = y
                            note.moveEvent(event)

    def mouseReleaseEvent(self, event):
        if not (any((key.pressed for key in self.piano_keys)) or any((note.pressed for note in self.notes))):
            if event.button() == Qt.LeftButton:
                if self.place_ghost and self.insert_mode:
                    self.place_ghost = False
                    note_start = self.get_note_start_from_x(self.ghost_rect.x())
                    note_num = self.get_note_num_from_y(self.ghost_rect.y())
                    note_length = self.get_note_length_from_x(self.ghost_rect.width())
                    self.drawNote(note_num, note_start, note_length, self.ghost_vel)
                    self.midievent.emit(["midievent-add", note_num, note_start, note_length, self.ghost_vel])
                    self.makeGhostNote(self.mousePos.x(), self.mousePos.y())
                elif self.marquee_select:
                    self.marquee_select = False
                    self.removeItem(self.marquee)
        elif not self.marquee_select:
            for note in self.selected_notes:
                old_info = note.note[:]
                note.mouseReleaseEvent(event)
                if self.velocity_mode:
                    note.setSelected(True)
                if not old_info == note.note:
                    self.midievent.emit(["midievent-remove", old_info[0], old_info[1], old_info[2], old_info[3]])
                    self.midievent.emit(["midievent-add", note.note[0], note.note[1], note.note[2], note.note[3]])

    # -------------------------------------------------------------------------
    # Internal Functions

    def drawHeader(self):
        self.header = QGraphicsRectItem(0, 0, self.grid_width, self.header_height)
        #self.header.setZValue(1.0)
        self.header.setPos(self.piano_width, 0)
        self.addItem(self.header)

    def drawPiano(self):
        piano_keys_width = self.piano_width - self.padding
        labels = ('B','Bb','A','Ab','G','Gb','F','E','Eb','D','Db','C')
        black_notes = (2,4,6,9,11)
        piano_label = QFont()
        piano_label.setPointSize(6)
        self.piano = QGraphicsRectItem(0, 0, piano_keys_width, self.piano_height)
        self.piano.setPos(0, self.header_height)
        self.addItem(self.piano)

        key = PianoKeyItem(piano_keys_width, self.note_height, self.piano)
        label = QGraphicsSimpleTextItem('C8', key)
        label.setPos(18, 1)
        label.setFont(piano_label)
        key.setBrush(QColor(255, 255, 255))
        for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1):
            for j in range(self.notes_in_octave, 0, -1):
                if j in black_notes:
                    key = PianoKeyItem(piano_keys_width/1.4, self.note_height, self.piano)
                    key.setBrush(QColor(0, 0, 0))
                    key.setZValue(1.0)
                    key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
                elif (j - 1) and (j + 1) in black_notes:
                    key = PianoKeyItem(piano_keys_width, self.note_height * 2, self.piano)
                    key.setBrush(QColor(255, 255, 255))
                    key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
                elif (j - 1) in black_notes:
                    key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, self.piano)
                    key.setBrush(QColor(255, 255, 255))
                    key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
                elif (j + 1) in black_notes:
                    key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, self.piano)
                    key.setBrush(QColor(255, 255, 255))
                    key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
                if j == 12:
                    label = QGraphicsSimpleTextItem('{}{}'.format(labels[j - 1], self.end_octave - i), key )
                    label.setPos(18, 6)
                    label.setFont(piano_label)
                self.piano_keys.append(key)

    def drawGrid(self):
        black_notes = [2,4,6,9,11]
        scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
        scale_bar.setPos(self.piano_width, 0)
        scale_bar.setBrush(QColor(100,100,100))
        clearpen = QPen(QColor(0,0,0,0))
        for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1):
            for j in range(self.notes_in_octave, 0, -1):
                scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
                scale_bar.setPos(self.piano_width, self.note_height * j + self.octave_height * (i - 1))
                scale_bar.setPen(clearpen)
                if j not in black_notes:
                    scale_bar.setBrush(QColor(120,120,120))
                else:
                    scale_bar.setBrush(QColor(100,100,100))

        measure_pen = QPen(QColor(0, 0, 0, 120), 3)
        half_measure_pen = QPen(QColor(0, 0, 0, 40), 2)
        line_pen = QPen(QColor(0, 0, 0, 40))
        for i in range(0, int(self.num_measures) + 1):
            measure = QGraphicsLineItem(0, 0, 0, self.piano_height + self.header_height - measure_pen.width(), self.header)
            measure.setPos(self.measure_width * i, 0.5 * measure_pen.width())
            measure.setPen(measure_pen)
            if i < self.num_measures:
                number = QGraphicsSimpleTextItem('%d' % (i + 1), self.header)
                number.setPos(self.measure_width * i + 5, 2)
                number.setBrush(Qt.white)
                for j in self.frange(0, self.time_sig[0]*self.grid_div/self.time_sig[1], 1.):
                    line = QGraphicsLineItem(0, 0, 0, self.piano_height, self.header)
                    line.setZValue(1.0)
                    line.setPos(self.measure_width * i + self.value_width * j, self.header_height)
                    if j == self.time_sig[0]*self.grid_div/self.time_sig[1] / 2.0:
                        line.setPen(half_measure_pen)
                    else:
                        line.setPen(line_pen)

    def drawPlayHead(self):
        self.play_head = QGraphicsLineItem(self.piano_width, self.header_height, self.piano_width, self.total_height)
        self.play_head.setPen(QPen(QColor(255,255,255,50), 2))
        self.play_head.setZValue(1.)
        self.addItem(self.play_head)

    def refreshScene(self):
        list(map(self.removeItem, self.notes))
        self.selected_notes = []
        self.piano_keys = []
        self.clear()
        self.drawPiano()
        self.drawHeader()
        self.drawGrid()
        self.drawPlayHead()
        for note in self.notes[:]:
            if note.note[1] >= (self.num_measures * self.time_sig[0]):
                self.notes.remove(note)
                self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
            elif note.note[2] > self.max_note_length:
                new_note = note.note[:]
                new_note[2] = self.max_note_length
                self.notes.remove(note)
                self.drawNote(new_note[0], new_note[1], self.max_note_length, new_note[3], False)
                self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
                self.midievent.emit(["midievent-add", new_note[0], new_note[1], new_note[2], new_note[3]])
        list(map(self.addItem, self.notes))
        if self.views():
            self.views()[0].setSceneRect(self.itemsBoundingRect())

    def clearNotes(self):
        self.clear()
        self.notes = []
        self.selected_notes = []
        self.drawPiano()
        self.drawHeader()
        self.drawGrid()

    def makeGhostNote(self, pos_x, pos_y):
        """creates the ghostnote that is placed on the scene before the real one is."""
        if self.ghost_note:
            self.removeItem(self.ghost_note)
        length = self.full_note_width * self.default_length
        (start, note) = self.snap(pos_x, pos_y)
        self.ghost_vel = self.default_ghost_vel
        self.ghost_rect = QRectF(start, note, length, self.note_height)
        self.ghost_rect_orig_width = self.ghost_rect.width()
        self.ghost_note = QGraphicsRectItem(self.ghost_rect)
        self.ghost_note.setBrush(QColor(230, 221, 45, 100))
        self.addItem(self.ghost_note)

    def drawNote(self, note_num, note_start=None, note_length=None, note_velocity=None, add=True):
        """
        note_num: midi number, 0 - 127
        note_start: 0 - (num_measures * time_sig[0]) so this is in beats
        note_length: 0 - (num_measures  * time_sig[0]/time_sig[1]) this is in measures
        note_velocity: 0 - 127
        """

        info = [note_num, note_start, note_length, note_velocity]

        if not note_start % (self.num_measures * self.time_sig[0]) == note_start:
            #self.midievent.emit(["midievent-remove", note_num, note_start, note_length, note_velocity])
            while not note_start % (self.num_measures * self.time_sig[0]) == note_start:
                self.setMeasures(self.num_measures+1)
            self.measureupdate.emit(self.num_measures)
            self.refreshScene()

        x_start = self.get_note_x_start(note_start)
        if note_length > self.max_note_length:
            note_length = self.max_note_length + 0.25
        x_length = self.get_note_x_length(note_length)
        y_pos = self.get_note_y_pos(note_num)

        note = NoteItem(self.note_height, x_length, info)
        note.setPos(x_start, y_pos)

        self.notes.append(note)
        if add:
            self.addItem(note)

    # -------------------------------------------------------------------------
    # Helper Functions

    def frange(self, x, y, t):
        while x < y:
            yield x
            x += t

    def quantize(self, value):
        self.snap_value = float(self.full_note_width) * value if value else None

    def snap(self, pos_x, pos_y = None):
        if self.snap_value:
            pos_x = int(round((pos_x - self.piano_width) / self.snap_value)) \
                    * self.snap_value + self.piano_width
        if pos_y:
            pos_y = int((pos_y - self.header_height) / self.note_height) \
                    * self.note_height + self.header_height
        return (pos_x, pos_y) if pos_y else pos_x

    def adjust_note_vel(self, event):
        m_pos = event.scenePos()
        #bind velocity to vertical mouse movement
        self.ghost_vel += (event.lastScenePos().y() - m_pos.y())/10
        if self.ghost_vel < 0:
            self.ghost_vel = 0
        elif self.ghost_vel > 127:
            self.ghost_vel = 127

        m_width = self.ghost_rect.x() + self.ghost_rect_orig_width
        if m_pos.x() < m_width:
            m_pos.setX(m_width)
        m_new_x = self.snap(m_pos.x())
        self.ghost_rect.setRight(m_new_x)
        self.ghost_note.setRect(self.ghost_rect)


    def enforce_bounds(self, pos):
        if pos.x() < self.piano_width:
            pos.setX(self.piano_width)
        elif pos.x() > self.grid_width + self.piano_width:
            pos.setX(self.grid_width + self.piano_width)
        if pos.y() < self.header_height + self.padding:
            pos.setY(self.header_height + self.padding)
        return pos

    def get_note_start_from_x(self, note_x):
        return (note_x - self.piano_width) / (self.grid_width / self.num_measures / self.time_sig[0])


    def get_note_x_start(self, note_start):
        return self.piano_width + \
                (self.grid_width / self.num_measures / self.time_sig[0]) * note_start

    def get_note_x_length(self, note_length):
        return float(self.time_sig[1]) / self.time_sig[0] * note_length * self.grid_width / self.num_measures

    def get_note_length_from_x(self, note_x):
        return float(self.time_sig[0]) / self.time_sig[1] * self.num_measures / self.grid_width \
                * note_x


    def get_note_y_pos(self, note_num):
        return self.header_height + self.note_height * (self.total_notes - note_num - 1)

    def get_note_num_from_y(self, note_y_pos):
        return -(((note_y_pos - self.header_height) / self.note_height) - self.total_notes + 1)
Beispiel #25
0
class PianoRoll(QGraphicsScene):
    '''the piano roll'''

    noteclicked = pyqtSignal(int,bool)
    midievent = pyqtSignal(list)
    measureupdate = pyqtSignal(int)
    modeupdate = pyqtSignal(str)

    default_ghost_vel = 100

    def __init__(self, time_sig = '4/4', num_measures = 4, quantize_val = '1/8'):
        QGraphicsScene.__init__(self)
        self.setBackgroundBrush(QColor(50, 50, 50))

        self.notes = []
        self.removed_notes = []
        self.selected_notes = []
        self.piano_keys = []

        self.marquee_select = False
        self.marquee_rect = None
        self.marquee = None

        self.ghost_note = None
        self.ghost_rect = None
        self.ghost_rect_orig_width = None
        self.ghost_vel = self.default_ghost_vel

        self.ignore_mouse_events = False
        self.insert_mode = False
        self.velocity_mode = False
        self.place_ghost = False
        self.last_mouse_pos = QPointF()

        ## dimensions
        self.padding = 2

        ## piano dimensions
        self.note_height = 10
        self.start_octave = -2
        self.end_octave = 8
        self.notes_in_octave = 12
        self.total_notes = (self.end_octave - self.start_octave) * self.notes_in_octave + 1
        self.piano_height = self.note_height * self.total_notes
        self.octave_height = self.notes_in_octave * self.note_height

        self.piano_width = 34

        ## height
        self.header_height = 20
        self.total_height = self.piano_height - self.note_height + self.header_height
        #not sure why note_height is subtracted

        ## width
        self.full_note_width = 250 # i.e. a 4/4 note
        self.snap_value = None
        self.quantize_val = quantize_val

        ### dummy vars that will be changed
        self.time_sig = (0,0)
        self.measure_width = 0
        self.num_measures = 0
        self.max_note_length = 0
        self.grid_width = 0
        self.value_width = 0
        self.grid_div = 0
        self.piano = None
        self.header = None
        self.play_head = None

        self.setGridDiv()
        self.default_length = 1. / self.grid_div


    # -------------------------------------------------------------------------
    # Callbacks

    def movePlayHead(self, transportInfo):
        ticksPerBeat = transportInfo['ticksPerBeat']
        max_ticks = ticksPerBeat * self.time_sig[0] * self.num_measures
        cur_tick = ticksPerBeat * self.time_sig[0] * transportInfo['bar'] + ticksPerBeat * transportInfo['beat'] + transportInfo['tick']
        frac = (cur_tick % max_ticks) / max_ticks
        self.play_head.setPos(QPointF(frac * self.grid_width, 0))

    def setTimeSig(self, time_sig):
        self.time_sig = time_sig
        self.measure_width = self.full_note_width * self.time_sig[0]/self.time_sig[1]
        self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
        self.grid_width = self.measure_width * self.num_measures
        self.setGridDiv()

    def setMeasures(self, measures):
        #try:
        self.num_measures = float(measures)
        self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
        self.grid_width = self.measure_width * self.num_measures
        self.refreshScene()
        #except:
            #pass

    def setDefaultLength(self, length):
        v = list(map(float, length.split('/')))
        if len(v) < 3:
            self.default_length = v[0] if len(v) == 1 else v[0] / v[1]
            pos = self.enforce_bounds(self.last_mouse_pos)
            if self.insert_mode:
                self.makeGhostNote(pos.x(), pos.y())

    def setGridDiv(self, div=None):
        if not div: div = self.quantize_val
        try:
            val = list(map(int, div.split('/')))
            if len(val) < 3:
                self.quantize_val = div
                self.grid_div = val[0] if len(val)==1 else val[1]
                self.value_width = self.full_note_width / float(self.grid_div) if self.grid_div else None
                self.setQuantize(div)

                self.refreshScene()
        except ValueError:
            pass

    def setQuantize(self, value):
        val = list(map(float, value.split('/')))
        if len(val) == 1:
            self.quantize(val[0])
            self.quantize_val = value
        elif len(val) == 2:
            self.quantize(val[0] / val[1])
            self.quantize_val = value

    # -------------------------------------------------------------------------
    # Event Callbacks

    def keyPressEvent(self, event):
        QGraphicsScene.keyPressEvent(self, event)

        if event.key() == Qt.Key_F:
            if not self.insert_mode:
                # turn off velocity mode
                self.velocity_mode = False
                # enable insert mode
                self.insert_mode = True
                self.place_ghost = False
                self.makeGhostNote(self.last_mouse_pos.x(), self.last_mouse_pos.y())
                self.modeupdate.emit('insert_mode')
            else:
                # turn off insert mode
                self.insert_mode = False
                self.place_ghost = False
                if self.ghost_note is not None:
                    self.removeItem(self.ghost_note)
                    self.ghost_note = None
                self.modeupdate.emit('')

        elif event.key() == Qt.Key_D:
            if not self.velocity_mode:
                # turn off insert mode
                self.insert_mode = False
                self.place_ghost = False
                if self.ghost_note is not None:
                    self.removeItem(self.ghost_note)
                    self.ghost_note = None
                # enable velocity mode
                self.velocity_mode = True
                self.modeupdate.emit('velocity_mode')
            else:
                # turn off velocity mode
                self.velocity_mode = False
                self.modeupdate.emit('')

        elif event.key() == Qt.Key_A:
            for note in self.notes:
                if not note.isSelected():
                    has_unselected = True
                    break
            else:
                has_unselected = False

            # select all notes
            if has_unselected:
                for note in self.notes:
                    note.setSelected(True)
                self.selected_notes = self.notes[:]
            # unselect all
            else:
                for note in self.notes:
                    note.setSelected(False)
                self.selected_notes = []

        elif event.key() in (Qt.Key_Delete, Qt.Key_Backspace):
            # remove selected notes from our notes list
            self.notes = [note for note in self.notes if note not in self.selected_notes]
            # delete the selected notes
            for note in self.selected_notes:
                self.removeItem(note)
                self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
                del note
            self.selected_notes = []

    def mousePressEvent(self, event):
        QGraphicsScene.mousePressEvent(self, event)

        # mouse click on left-side piano area
        if self.piano.contains(event.scenePos()):
            self.ignore_mouse_events = True
            return

        clicked_notes = []

        for note in self.notes:
            if note.pressed or note.back.stretch or note.front.stretch:
                clicked_notes.append(note)

        print("clicked_notes", clicked_notes)

        # mouse click on existing notes
        if clicked_notes:
            keep_selection = all(note in self.selected_notes for note in clicked_notes)
            if keep_selection:
                for note in self.selected_notes:
                    note.setSelected(True)
                return

            for note in self.selected_notes:
                if note not in clicked_notes:
                    note.setSelected(False)
            for note in clicked_notes:
                if note not in self.selected_notes:
                    note.setSelected(True)

            self.selected_notes = clicked_notes
            return

        # mouse click on empty area (no note selected)
        for note in self.selected_notes:
            note.setSelected(False)
        self.selected_notes = []

        if event.button() != Qt.LeftButton:
            return

        if self.insert_mode:
            self.place_ghost = True
        else:
            self.marquee_select = True
            self.marquee_rect = QRectF(event.scenePos().x(), event.scenePos().y(), 1, 1)
            self.marquee = QGraphicsRectItem(self.marquee_rect)
            self.marquee.setBrush(QColor(255, 255, 255, 100))
            self.addItem(self.marquee)

    def mouseMoveEvent(self, event):
        QGraphicsScene.mouseMoveEvent(self, event)

        self.last_mouse_pos = event.scenePos()

        if self.ignore_mouse_events:
            return

        pos = self.enforce_bounds(self.last_mouse_pos)

        if self.insert_mode:
            if self.ghost_note is None:
                self.makeGhostNote(pos.x(), pos.y())
            max_x = self.grid_width + self.piano_width

            # placing note, only width needs updating
            if self.place_ghost:
                pos_x = pos.x()
                min_x = self.ghost_rect.x() + self.ghost_rect_orig_width
                if pos_x < min_x:
                    pos_x = min_x
                new_x = self.snap(pos_x)
                self.ghost_rect.setRight(new_x)
                self.ghost_note.setRect(self.ghost_rect)
                #self.adjust_note_vel(event)

            # ghostnote following mouse around
            else:
                pos_x = pos.x()
                if pos_x + self.ghost_rect.width() >= max_x:
                    pos_x = max_x - self.ghost_rect.width()
                elif pos_x > self.piano_width + self.ghost_rect.width()*3/4:
                    pos_x -= self.ghost_rect.width()/2
                new_x, new_y = self.snap(pos_x, pos.y())
                self.ghost_rect.moveTo(new_x, new_y)
                self.ghost_note.setRect(self.ghost_rect)
            return

        if self.marquee_select:
            marquee_orig_pos = event.buttonDownScenePos(Qt.LeftButton)
            if marquee_orig_pos.x() < pos.x() and marquee_orig_pos.y() < pos.y():
                self.marquee_rect.setBottomRight(pos)
            elif marquee_orig_pos.x() < pos.x() and marquee_orig_pos.y() > pos.y():
                self.marquee_rect.setTopRight(pos)
            elif marquee_orig_pos.x() > pos.x() and marquee_orig_pos.y() < pos.y():
                self.marquee_rect.setBottomLeft(pos)
            elif marquee_orig_pos.x() > pos.x() and marquee_orig_pos.y() > pos.y():
                self.marquee_rect.setTopLeft(pos)
            self.marquee.setRect(self.marquee_rect)

            for note in self.selected_notes:
                note.setSelected(False)
            self.selected_notes = []

            for item in self.collidingItems(self.marquee):
                if item in self.notes:
                    item.setSelected(True)
                    self.selected_notes.append(item)
            return

        if event.buttons() != Qt.LeftButton:
            return

        if self.velocity_mode:
            for note in self.selected_notes:
                note.updateVelocity(event)
            return

        x = y = False
        for note in self.selected_notes:
            if note.back.stretch:
                x = True
                break
        for note in self.selected_notes:
            if note.front.stretch:
                y = True
                break
        for note in self.selected_notes:
            note.back.stretch = x
            note.front.stretch = y
            note.moveEvent(event)

    def mouseReleaseEvent(self, event):
        QGraphicsScene.mouseReleaseEvent(self, event)

        if self.ignore_mouse_events:
            self.ignore_mouse_events = False
            return

        if self.marquee_select:
            self.marquee_select = False
            self.removeItem(self.marquee)
            self.marquee = None

        if self.insert_mode and self.place_ghost:
            self.place_ghost = False
            note_start = self.get_note_start_from_x(self.ghost_rect.x())
            note_num = self.get_note_num_from_y(self.ghost_rect.y())
            note_length = self.get_note_length_from_x(self.ghost_rect.width())
            note = self.drawNote(note_num, note_start, note_length, self.ghost_vel)
            note.setSelected(True)
            self.selected_notes.append(note)
            self.midievent.emit(["midievent-add", note_num, note_start, note_length, self.ghost_vel])
            pos = self.enforce_bounds(self.last_mouse_pos)
            pos_x = pos.x()
            if pos_x > self.piano_width + self.ghost_rect.width()*3/4:
                pos_x -= self.ghost_rect.width()/2
            self.makeGhostNote(pos_x, pos.y())

        for note in self.selected_notes:
            note.back.stretch = False
            note.front.stretch = False

    # -------------------------------------------------------------------------
    # Internal Functions

    def drawHeader(self):
        self.header = QGraphicsRectItem(0, 0, self.grid_width, self.header_height)
        #self.header.setZValue(1.0)
        self.header.setPos(self.piano_width, 0)
        self.addItem(self.header)

    def drawPiano(self):
        piano_keys_width = self.piano_width - self.padding
        labels = ('B','Bb','A','Ab','G','Gb','F','E','Eb','D','Db','C')
        black_notes = (2,4,6,9,11)
        piano_label = QFont()
        piano_label.setPointSize(6)
        self.piano = QGraphicsRectItem(0, 0, piano_keys_width, self.piano_height)
        self.piano.setPos(0, self.header_height)
        self.addItem(self.piano)

        key = PianoKeyItem(piano_keys_width, self.note_height, 78, self.piano)
        label = QGraphicsSimpleTextItem('C9', key)
        label.setPos(18, 1)
        label.setFont(piano_label)
        key.setBrush(QColor(255, 255, 255))
        for i in range(self.end_octave - self.start_octave, 0, -1):
            for j in range(self.notes_in_octave, 0, -1):
                note = (self.end_octave - i + 3) * 12 - j
                if j in black_notes:
                    key = PianoKeyItem(piano_keys_width/1.4, self.note_height, note, self.piano)
                    key.setBrush(QColor(0, 0, 0))
                    key.setZValue(1.0)
                    key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
                elif (j - 1) and (j + 1) in black_notes:
                    key = PianoKeyItem(piano_keys_width, self.note_height * 2, note, self.piano)
                    key.setBrush(QColor(255, 255, 255))
                    key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
                elif (j - 1) in black_notes:
                    key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, note, self.piano)
                    key.setBrush(QColor(255, 255, 255))
                    key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
                elif (j + 1) in black_notes:
                    key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, note, self.piano)
                    key.setBrush(QColor(255, 255, 255))
                    key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
                if j == 12:
                    label = QGraphicsSimpleTextItem('{}{}'.format(labels[j - 1], self.end_octave - i + 1), key)
                    label.setPos(18, 6)
                    label.setFont(piano_label)
                self.piano_keys.append(key)

    def drawGrid(self):
        black_notes = [2,4,6,9,11]
        scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
        scale_bar.setPos(self.piano_width, 0)
        scale_bar.setBrush(QColor(100,100,100))
        clearpen = QPen(QColor(0,0,0,0))
        for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1):
            for j in range(self.notes_in_octave, 0, -1):
                scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
                scale_bar.setPos(self.piano_width, self.note_height * j + self.octave_height * (i - 1))
                scale_bar.setPen(clearpen)
                if j not in black_notes:
                    scale_bar.setBrush(QColor(120,120,120))
                else:
                    scale_bar.setBrush(QColor(100,100,100))

        measure_pen = QPen(QColor(0, 0, 0, 120), 3)
        half_measure_pen = QPen(QColor(0, 0, 0, 40), 2)
        line_pen = QPen(QColor(0, 0, 0, 40))
        for i in range(0, int(self.num_measures) + 1):
            measure = QGraphicsLineItem(0, 0, 0, self.piano_height + self.header_height - measure_pen.width(), self.header)
            measure.setPos(self.measure_width * i, 0.5 * measure_pen.width())
            measure.setPen(measure_pen)
            if i < self.num_measures:
                number = QGraphicsSimpleTextItem('%d' % (i + 1), self.header)
                number.setPos(self.measure_width * i + 5, 2)
                number.setBrush(Qt.white)
                for j in self.frange(0, self.time_sig[0]*self.grid_div/self.time_sig[1], 1.):
                    line = QGraphicsLineItem(0, 0, 0, self.piano_height, self.header)
                    line.setZValue(1.0)
                    line.setPos(self.measure_width * i + self.value_width * j, self.header_height)
                    if j == self.time_sig[0]*self.grid_div/self.time_sig[1] / 2.0:
                        line.setPen(half_measure_pen)
                    else:
                        line.setPen(line_pen)

    def drawPlayHead(self):
        self.play_head = QGraphicsLineItem(self.piano_width, self.header_height, self.piano_width, self.total_height)
        self.play_head.setPen(QPen(QColor(255,255,255,50), 2))
        self.play_head.setZValue(1.)
        self.addItem(self.play_head)

    def refreshScene(self):
        list(map(self.removeItem, self.notes))
        self.selected_notes = []
        self.piano_keys = []
        self.place_ghost = False
        if self.ghost_note is not None:
            self.removeItem(self.ghost_note)
            self.ghost_note = None
        self.clear()
        self.drawPiano()
        self.drawHeader()
        self.drawGrid()
        self.drawPlayHead()

        for note in self.notes[:]:
            if note.note[1] >= (self.num_measures * self.time_sig[0]):
                self.notes.remove(note)
                self.removed_notes.append(note)
                #self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
            elif note.note[2] > self.max_note_length:
                new_note = note.note[:]
                new_note[2] = self.max_note_length
                self.notes.remove(note)
                self.drawNote(new_note[0], new_note[1], self.max_note_length, new_note[3], False)
                self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
                self.midievent.emit(["midievent-add", new_note[0], new_note[1], new_note[2], new_note[3]])

        for note in self.removed_notes[:]:
            if note.note[1] < (self.num_measures * self.time_sig[0]):
                self.removed_notes.remove(note)
                self.notes.append(note)

        list(map(self.addItem, self.notes))
        if self.views():
            self.views()[0].setSceneRect(self.itemsBoundingRect())

    def clearNotes(self):
        self.clear()
        self.notes = []
        self.removed_notes = []
        self.selected_notes = []
        self.drawPiano()
        self.drawHeader()
        self.drawGrid()

    def makeGhostNote(self, pos_x, pos_y):
        """creates the ghostnote that is placed on the scene before the real one is."""
        if self.ghost_note is not None:
            self.removeItem(self.ghost_note)
        length = self.full_note_width * self.default_length
        pos_x, pos_y = self.snap(pos_x, pos_y)
        self.ghost_vel = self.default_ghost_vel
        self.ghost_rect = QRectF(pos_x, pos_y, length, self.note_height)
        self.ghost_rect_orig_width = self.ghost_rect.width()
        self.ghost_note = QGraphicsRectItem(self.ghost_rect)
        self.ghost_note.setBrush(QColor(230, 221, 45, 100))
        self.addItem(self.ghost_note)

    def drawNote(self, note_num, note_start, note_length, note_velocity, add=True):
        """
        note_num: midi number, 0 - 127
        note_start: 0 - (num_measures * time_sig[0]) so this is in beats
        note_length: 0 - (num_measures  * time_sig[0]/time_sig[1]) this is in measures
        note_velocity: 0 - 127
        """

        info = [note_num, note_start, note_length, note_velocity]

        if not note_start % (self.num_measures * self.time_sig[0]) == note_start:
            #self.midievent.emit(["midievent-remove", note_num, note_start, note_length, note_velocity])
            while not note_start % (self.num_measures * self.time_sig[0]) == note_start:
                self.setMeasures(self.num_measures+1)
            self.measureupdate.emit(self.num_measures)
            self.refreshScene()

        x_start = self.get_note_x_start(note_start)
        if note_length > self.max_note_length:
            note_length = self.max_note_length + 0.25
        x_length = self.get_note_x_length(note_length)
        y_pos = self.get_note_y_pos(note_num)

        note = NoteItem(self.note_height, x_length, info)
        note.setPos(x_start, y_pos)

        self.notes.append(note)

        if add:
            self.addItem(note)

        return note

    # -------------------------------------------------------------------------
    # Helper Functions

    def frange(self, x, y, t):
        while x < y:
            yield x
            x += t

    def quantize(self, value):
        self.snap_value = float(self.full_note_width) * value if value else None

    def snap(self, pos_x, pos_y = None):
        if self.snap_value:
            pos_x = int(round((pos_x - self.piano_width) / self.snap_value)) * self.snap_value + self.piano_width
        if pos_y is not None:
            pos_y = int((pos_y - self.header_height) / self.note_height) * self.note_height + self.header_height
        return (pos_x, pos_y) if pos_y is not None else pos_x

    def adjust_note_vel(self, event):
        m_pos = event.scenePos()
        #bind velocity to vertical mouse movement
        self.ghost_vel += (event.lastScenePos().y() - m_pos.y())/10
        if self.ghost_vel < 0:
            self.ghost_vel = 0
        elif self.ghost_vel > 127:
            self.ghost_vel = 127

        m_width = self.ghost_rect.x() + self.ghost_rect_orig_width
        if m_pos.x() < m_width:
            m_pos.setX(m_width)
        m_new_x = self.snap(m_pos.x())
        self.ghost_rect.setRight(m_new_x)
        self.ghost_note.setRect(self.ghost_rect)

    def enforce_bounds(self, pos):
        pos = QPointF(pos)
        if pos.x() < self.piano_width:
            pos.setX(self.piano_width)
        elif pos.x() >= self.grid_width + self.piano_width:
            pos.setX(self.grid_width + self.piano_width - 1)
        if pos.y() < self.header_height + self.padding:
            pos.setY(self.header_height + self.padding)
        return pos

    def get_note_start_from_x(self, note_x):
        return (note_x - self.piano_width) / (self.grid_width / self.num_measures / self.time_sig[0])

    def get_note_x_start(self, note_start):
        return self.piano_width + (self.grid_width / self.num_measures / self.time_sig[0]) * note_start

    def get_note_x_length(self, note_length):
        return float(self.time_sig[1]) / self.time_sig[0] * note_length * self.grid_width / self.num_measures

    def get_note_length_from_x(self, note_x):
        return float(self.time_sig[0]) / self.time_sig[1] * self.num_measures / self.grid_width * note_x

    def get_note_y_pos(self, note_num):
        return self.header_height + self.note_height * (self.total_notes - note_num - 1)

    def get_note_num_from_y(self, note_y_pos):
        return -(int((note_y_pos - self.header_height) / self.note_height) - self.total_notes + 1)

    def move_note(self, old_note, new_note):
        self.midievent.emit(["midievent-remove", old_note[0], old_note[1], old_note[2], old_note[3]])
        self.midievent.emit(["midievent-add", new_note[0], new_note[1], new_note[2], new_note[3]])
Beispiel #26
0
class ImageViewer(QGraphicsView, QObject):
    points_selection_sgn = pyqtSignal(list)
    key_press_sgn = pyqtSignal(QtGui.QKeyEvent)

    def __init__(self, parent=None):
        super(ImageViewer, self).__init__(parent)
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        self.setRenderHints(QPainter.Antialiasing
                            | QPainter.SmoothPixmapTransform)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)

        self._scene = ImageViewerScene(self)
        self.setScene(self._scene)
        self._image = None
        self._image_original = None
        self._pixmap = None
        self._img_contrast = 1.0
        self._img_brightness = 50.0
        self._img_gamma = 1.0
        self._create_grid()
        self._channels = []
        self._current_tool = SELECTION_TOOL.POINTER
        self._dataset = None

        # create grid lines
        pen_color = QColor(255, 255, 255, 255)
        pen = QPen(pen_color)
        pen.setWidth(2)
        pen.setStyle(QtCore.Qt.DotLine)
        self.vline = QGraphicsLineItem()
        self.vline.setVisible(False)
        self.vline.setPen(pen)
        self.hline = QGraphicsLineItem()
        self.hline.setVisible(False)
        self.hline.setPen(pen)
        self._scene.addItem(self.vline)
        self._scene.addItem(self.hline)
        self._current_label = None

        # rectangle selection tool
        self._rectangle_tool_origin = QPoint()
        self._rectangle_tool_picker = QRubberBand(QRubberBand.Rectangle, self)

        # polygon selection tool
        app = QApplication.instance()
        color = app.palette().color(QPalette.Highlight)
        self._polygon_guide_line_pen = QPen(color)
        self._polygon_guide_line_pen.setWidth(3)
        self._polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine)
        self._polygon_guide_line = QGraphicsLineItem()
        self._polygon_guide_line.setVisible(False)
        self._polygon_guide_line.setPen(self._polygon_guide_line_pen)
        self._scene.addItem(self._polygon_guide_line)
        self._current_polygon = None

        # circle
        self._current_ellipse = None

        # free selection tool
        self._current_free_path = None
        self._is_drawing = False
        self._last_point_drawn = QPoint()
        self._last_click_point = None
        self._free_Path_pen = QPen(color)
        self._free_Path_pen.setWidth(10)

        self._extreme_points = Queue(maxsize=4)

    @property
    def current_label(self):
        return self._current_label

    @current_label.setter
    def current_label(self, value):
        self._current_label = value
        if self._current_label:
            color = QColor(self._current_label.color)
            self._free_Path_pen.setColor(color)
            self._polygon_guide_line_pen.setColor(color)
            self._polygon_guide_line.setPen(self._polygon_guide_line_pen)

    @property
    def dataset(self):
        return self._dataset

    @dataset.setter
    def dataset(self, value):
        self._dataset = value

    @property
    def img_contrast(self):
        return self._img_contrast

    @img_contrast.setter
    def img_contrast(self, value):
        self._img_contrast = value

    @property
    def img_gamma(self):
        return self._img_gamma

    @img_gamma.setter
    def img_gamma(self, value):
        self._img_gamma = value

    @property
    def img_brightness(self):
        return self._img_brightness

    @img_brightness.setter
    def img_brightness(self, value):
        self._img_brightness = value

    @property
    def image(self):
        return self._image

    @image.setter
    def image(self, value):
        self._image = value
        self._image_original = value.copy()
        self.update_viewer()

    @property
    def pixmap(self) -> ImagePixmap:
        return self._pixmap

    @gui_exception
    def update_viewer(self, fit_image=True):
        rgb = cv2.cvtColor(self._image, cv2.COLOR_BGR2RGB)
        rgb = ImageUtilities.adjust_image(rgb, self._img_contrast,
                                          self._img_brightness)
        rgb = ImageUtilities.adjust_gamma(rgb, self._img_gamma)
        pil_image = Image.fromarray(rgb)
        qppixmap_image = pil_image.toqpixmap()
        x, y = -qppixmap_image.width() / 2, -qppixmap_image.height() / 2

        if self._pixmap:
            self._pixmap.resetTransform()
            self._pixmap.setPixmap(qppixmap_image)
            self._pixmap.setOffset(x, y)
        else:
            self._pixmap = ImagePixmap()
            self._pixmap.setPixmap(qppixmap_image)
            self._pixmap.setOffset(x, y)
            self._scene.addItem(self._pixmap)
            self._pixmap.signals.hoverEnterEventSgn.connect(
                self.pixmap_hoverEnterEvent_slot)
            self._pixmap.signals.hoverLeaveEventSgn.connect(
                self.pixmap_hoverLeaveEvent_slot)
            self._pixmap.signals.hoverMoveEventSgn.connect(
                self.pixmap_hoverMoveEvent_slot)
        self._hide_guide_lines()
        if fit_image:
            self.fit_to_window()

    @gui_exception
    def reset_viewer(self):
        self._img_contrast = 1.0
        self._img_brightness = 50.0
        self._img_gamma = 1.0
        self._image = self._image_original.copy()

    @gui_exception
    def equalize_histogram(self):
        self._image = ImageUtilities.histogram_equalization(self._image)

    @gui_exception
    def correct_lightness(self):
        self._image = ImageUtilities.correct_lightness(self._image)

    def clusterize(self, k):
        self._image = ImageUtilities.kmeans(self._image.copy(), k)

    @property
    def current_tool(self):
        return self._current_tool

    @current_tool.setter
    def current_tool(self, value):
        self._polygon_guide_line.hide()
        self._current_polygon = None
        self._current_free_path = None
        self._current_ellipse = None
        self._is_drawing = value == SELECTION_TOOL.FREE
        self._current_tool = value
        self.clear_extreme_points()
        if value == SELECTION_TOOL.POINTER:
            self.enable_items(True)
        else:
            self.enable_items(False)

    def fit_to_window(self):
        if not self._pixmap or not self._pixmap.pixmap():
            return
        self.resetTransform()
        self.setTransform(QtGui.QTransform())
        self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio)

    def _create_grid(self, gridSize=15):
        app: QApplication = QApplication.instance()
        curr_theme = "dark"
        if app:
            curr_theme = app.property("theme")
        if curr_theme == "light":
            color1 = QtGui.QColor("white")
            color2 = QtGui.QColor(237, 237, 237)
        else:
            color1 = QtGui.QColor(20, 20, 20)
            color2 = QtGui.QColor(0, 0, 0)
        backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2)
        backgroundPixmap.fill(color1)
        painter = QtGui.QPainter(backgroundPixmap)
        painter.fillRect(0, 0, gridSize, gridSize, color2)
        painter.fillRect(gridSize, gridSize, gridSize, gridSize, color2)
        painter.end()
        self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap))

    def wheelEvent(self, event: QWheelEvent):
        adj = (event.angleDelta().y() / 120) * 0.1
        self.scale(1 + adj, 1 + adj)

    @gui_exception
    def keyPressEvent(self, event: QKeyEvent):
        if event.key() == QtCore.Qt.Key_Space:
            image_rect: QRectF = self._pixmap.sceneBoundingRect()
            if self.current_tool == SELECTION_TOOL.POLYGON and self._current_polygon:
                points = self._current_polygon.points
                self._polygon_guide_line.hide()
                self.setDragMode(QGraphicsView.ScrollHandDrag)
                if len(points) <= 2:
                    self._current_polygon.delete_item()
                self.current_tool = SELECTION_TOOL.POINTER
            elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS and \
                    self._extreme_points.full():
                points = []
                image_offset = QPointF(image_rect.width() / 2,
                                       image_rect.height() / 2)
                for pt in self._extreme_points.queue:
                    pt: EditablePolygonPoint
                    center = pt.sceneBoundingRect().center()
                    x = math.floor(center.x() + image_offset.x())
                    y = math.floor(center.y() + image_offset.y())
                    points.append([x, y])
                self.points_selection_sgn.emit(points)
                self.current_tool = SELECTION_TOOL.POINTER
        else:
            event.ignore()

    # guide lines events
    def _show_guide_lines(self):
        if self.hline and self.vline:
            self.hline.show()
            self.vline.show()

    def _hide_guide_lines(self):
        if self.hline and self.vline:
            self.hline.hide()
            self.vline.hide()

    def _update_guide_lines(self, x, y):
        bbox: QRect = self._pixmap.boundingRect()
        offset = QPointF(bbox.width() / 2, bbox.height() / 2)
        self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y())
        self.vline.setZValue(1)
        self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y)
        self.hline.setZValue(1)

    def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y):
        self._update_guide_lines(x, y)

    def pixmap_hoverEnterEvent_slot(self):
        self._show_guide_lines()

    def pixmap_hoverLeaveEvent_slot(self):
        self._hide_guide_lines()

    def delete_polygon_slot(self, polygon: EditablePolygon):
        self._current_polygon = None
        self.current_tool = SELECTION_TOOL.POINTER
        self._polygon_guide_line.hide()

    @gui_exception
    def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:
        image_rect: QRectF = self._pixmap.boundingRect()
        mouse_pos = self.mapToScene(evt.pos())
        if evt.buttons() == QtCore.Qt.LeftButton:
            if self.current_tool == SELECTION_TOOL.BOX:
                # create rectangle
                self.setDragMode(QGraphicsView.NoDrag)
                self._rectangle_tool_origin = evt.pos()
                geometry = QRect(self._rectangle_tool_origin, QSize())
                self._rectangle_tool_picker.setGeometry(geometry)
                self._rectangle_tool_picker.show()
            elif self.current_tool == SELECTION_TOOL.POLYGON:
                if image_rect.contains(mouse_pos):
                    if self._current_polygon is None:
                        self._current_polygon = EditablePolygon()
                        self._current_polygon.label = self._current_label
                        self._current_polygon.tag = self._dataset
                        self._current_polygon.signals.deleted.connect(
                            self.delete_polygon_slot)
                        self._scene.addItem(self._current_polygon)
                        self._current_polygon.addPoint(mouse_pos)
                    else:
                        self._current_polygon.addPoint(mouse_pos)
            elif self.current_tool == SELECTION_TOOL.ELLIPSE:
                if image_rect.contains(mouse_pos):
                    self.setDragMode(QGraphicsView.NoDrag)
                    ellipse_rec = QtCore.QRectF(mouse_pos.x(), mouse_pos.y(),
                                                0, 0)
                    self._current_ellipse = EditableEllipse()
                    self._current_ellipse.tag = self.dataset
                    self._current_ellipse.label = self._current_label
                    self._current_ellipse.setRect(ellipse_rec)
                    self._scene.addItem(self._current_ellipse)

            elif self.current_tool == SELECTION_TOOL.FREE:
                # consider only the points into the image
                if image_rect.contains(mouse_pos):
                    self.setDragMode(QGraphicsView.NoDrag)
                    self._last_point_drawn = mouse_pos
                    self._current_free_path = QGraphicsPathItem()
                    self._current_free_path.setOpacity(0.6)
                    self._current_free_path.setPen(self._free_Path_pen)
                    painter = QPainterPath()
                    painter.moveTo(self._last_point_drawn)
                    self._current_free_path.setPath(painter)
                    self._scene.addItem(self._current_free_path)

            elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS:
                if image_rect.contains(mouse_pos):
                    if not self._extreme_points.full():

                        def delete_point(idx):
                            del self._extreme_points.queue[idx]

                        idx = self._extreme_points.qsize()
                        editable_pt = EditablePolygonPoint(idx)
                        editable_pt.signals.deleted.connect(delete_point)
                        editable_pt.setPos(mouse_pos)
                        self._scene.addItem(editable_pt)
                        self._extreme_points.put(editable_pt)

        else:
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).mousePressEvent(evt)

    @gui_exception
    def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None:
        mouse_pos = self.mapToScene(evt.pos())
        image_rect: QRectF = self._pixmap.boundingRect()
        if self.current_tool == SELECTION_TOOL.BOX:
            if not self._rectangle_tool_origin.isNull():
                geometry = QRect(self._rectangle_tool_origin,
                                 evt.pos()).normalized()
                self._rectangle_tool_picker.setGeometry(geometry)
        elif self.current_tool == SELECTION_TOOL.POLYGON:
            if self._current_polygon and image_rect.contains(mouse_pos):
                if self._current_polygon.count > 0:
                    last_point: QPointF = self._current_polygon.last_point
                    self._polygon_guide_line.setZValue(1)
                    self._polygon_guide_line.show()
                    mouse_pos = self.mapToScene(evt.pos())
                    self._polygon_guide_line.setLine(last_point.x(),
                                                     last_point.y(),
                                                     mouse_pos.x(),
                                                     mouse_pos.y())
            else:
                self._polygon_guide_line.hide()
        elif self.current_tool == SELECTION_TOOL.ELLIPSE:
            if self._current_ellipse and image_rect.contains(mouse_pos):
                ellipse_rect = self._current_ellipse.rect()
                ellipse_pos = QPointF(ellipse_rect.x(), ellipse_rect.y())
                distance = math.hypot(mouse_pos.x() - ellipse_pos.x(),
                                      mouse_pos.y() - ellipse_pos.y())
                ellipse_rect.setWidth(distance)
                ellipse_rect.setHeight(distance)
                self._current_ellipse.setRect(ellipse_rect)
        elif self.current_tool == SELECTION_TOOL.FREE and evt.buttons(
        ) and QtCore.Qt.LeftButton:
            if self._current_free_path and image_rect.contains(mouse_pos):
                painter: QPainterPath = self._current_free_path.path()
                self._last_point_drawn = self.mapToScene(evt.pos())
                painter.lineTo(self._last_point_drawn)
                self._current_free_path.setPath(painter)
        super(ImageViewer, self).mouseMoveEvent(evt)

    @gui_exception
    def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None:
        image_rect: QRectF = self._pixmap.boundingRect()
        if self.current_tool == SELECTION_TOOL.BOX:
            roi: QRect = self._rectangle_tool_picker.geometry()
            roi: QRectF = self.mapToScene(roi).boundingRect()
            self._rectangle_tool_picker.hide()
            if image_rect == roi.united(image_rect):
                rect = EditableBox(roi)
                rect.label = self.current_label
                rect.tag = self._dataset
                self._scene.addItem(rect)
                self.current_tool = SELECTION_TOOL.POINTER
                self.setDragMode(QGraphicsView.ScrollHandDrag)

        elif self.current_tool == SELECTION_TOOL.ELLIPSE and self._current_ellipse:
            roi: QRect = self._current_ellipse.boundingRect()
            if image_rect == roi.united(image_rect):
                self.current_tool = SELECTION_TOOL.POINTER
                self.setDragMode(QGraphicsView.ScrollHandDrag)
            else:
                self._current_ellipse.delete_item()
        elif self.current_tool == SELECTION_TOOL.FREE and self._current_free_path:
            # create polygon
            self._current_free_path: QGraphicsPathItem
            path_rect = self._current_free_path.boundingRect()
            if image_rect == path_rect.united(image_rect):
                path = self._current_free_path.path()
                path_polygon = EditablePolygon()
                path_polygon.tag = self.dataset
                path_polygon.label = self.current_label
                self._scene.addItem(path_polygon)
                for i in range(0, path.elementCount(), 10):
                    x, y = path.elementAt(i).x, path.elementAt(i).y
                    path_polygon.addPoint(QPointF(x, y))
            self._scene.removeItem(self._current_free_path)
            self.current_tool = SELECTION_TOOL.POINTER
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).mouseReleaseEvent(evt)

    def remove_annotations(self):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                item.delete_item()

    def remove_annotations_by_label(self, label_name):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                if item.label and item.label.name == label_name:
                    item.delete_item()

    def enable_items(self, value):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                item.setEnabled(value)

    def clear_extreme_points(self):
        if self._extreme_points.qsize() > 0:
            for pt in self._extreme_points.queue:
                self._scene.removeItem(pt)
            self._extreme_points.queue.clear()
Beispiel #27
0
class painter(QGraphicsView):
    pixelSize = int(15 / narrowRatio)
    width = int(480 / narrowRatio)
    height = int(360 / narrowRatio)
    fontSize = int(15 / narrowRatio)
    anchorLineSize = int(100 / narrowRatio)
    ellipseRadius = int(8 / narrowRatio)
    textInterval = int(90 / narrowRatio)
    col = width / pixelSize
    line = height / pixelSize
    centerIndex = int(round(((line / 2 - 1) * col) + col / 2))
    frameCount = 0
    failedCount = 0
    baseZValue = 0
    mode = 1
    body = 1
    open_status = 0
    textLineHeight = fontSize + 10
    blurRaduis = 50  # Smoother improvement

    def __init__(self, dataThread):
        super(painter, self).__init__()
        self.dataThread = dataThread
        self.setFixedSize(self.width + 200, self.height + self.textLineHeight)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scene = QGraphicsScene()
        self.setScene(self.scene)
        # center het text item
        self.centerTextItem = QGraphicsTextItem()
        self.centerTextItem.setPos((self.width + 200) / 2 - self.fontSize, 0)
        self.centerTextItem.setZValue(self.baseZValue + 1)
        self.scene.addItem(self.centerTextItem)
        # version text item
        self.versionTextItem = QGraphicsTextItem()
        self.versionTextItem.setPos(self.width * 0.8 - self.fontSize,
                                    self.height * 0.8 - self.fontSize)
        self.versionTextItem.setZValue(self.baseZValue + 1)
        self.scene.addItem(self.versionTextItem)
        self.userCom = None

        # center anchor item
        centerX = self.width / 2
        centerY = self.height / 2
        self.ellipseItem = QGraphicsEllipseItem(0, 0, self.ellipseRadius * 2,
                                                self.ellipseRadius * 2)
        self.horLineItem = QGraphicsLineItem(0, 0, self.anchorLineSize, 0)
        self.verLineItem = QGraphicsLineItem(0, 0, 0, self.anchorLineSize)
        self.ellipseItem.setPos(centerX - self.ellipseRadius,
                                centerY - self.ellipseRadius)
        self.horLineItem.setPos(centerX - self.anchorLineSize / 2, centerY)
        self.verLineItem.setPos(centerX, centerY - self.anchorLineSize / 2)
        self.ellipseItem.setPen(QColor(Qt.white))
        self.horLineItem.setPen(QColor(Qt.white))
        self.verLineItem.setPen(QColor(Qt.white))
        self.ellipseItem.setZValue(self.baseZValue + 1)
        self.horLineItem.setZValue(self.baseZValue + 1)
        self.verLineItem.setZValue(self.baseZValue + 1)
        self.scene.addItem(self.ellipseItem)
        self.scene.addItem(self.horLineItem)
        self.scene.addItem(self.verLineItem)
        # camera item
        self.cameraBuffer = QPixmap(self.width,
                                    self.height + self.textLineHeight)
        self.cameraItem = QGraphicsPixmapItem()
        if useBlur:
            self.gusBlurEffect = QGraphicsBlurEffect()
            self.gusBlurEffect.setBlurRadius(self.blurRaduis)
            self.cameraItem.setGraphicsEffect(self.gusBlurEffect)
        self.cameraItem.setPos(100, 0)
        self.cameraItem.setZValue(self.baseZValue)
        self.scene.addItem(self.cameraItem)
        # het text item
        self.hetTextBuffer = QPixmap(self.width + 200, self.textLineHeight)
        self.hetTextItem = QGraphicsPixmapItem()
        self.hetTextItem.setPos(0, self.height)
        self.hetTextItem.setZValue(self.baseZValue)
        self.scene.addItem(self.hetTextItem)
        # button item
        self.ctrlOpenButton = QPushButton('Open', self)
        self.ctrlOpenButton.clicked.connect(self.ctrlOpen)
        self.ctrlOpenButton.setGeometry(10, 30, 100, 40)

        self.EvaluateButton = QPushButton('None', self)
        self.EvaluateButton.clicked.connect(self.evaluate)
        self.EvaluateButton.setGeometry(10, 80, 100, 40)

        self.getOnePicButton = QPushButton('Get a frame', self)
        self.getOnePicButton.clicked.connect(self.ctrlSendone)
        self.getOnePicButton.setGeometry(10, 130, 100, 40)
        self.getOnePicButton.setEnabled(False)

        self.modeManualButton = QPushButton('Auto', self)
        self.modeManualButton.clicked.connect(self.modeManual)
        self.modeManualButton.setGeometry(10, 180, 100, 40)

        self.modeObjButton = QPushButton('Body', self)
        self.modeObjButton.clicked.connect(self.objBody)
        self.modeObjButton.setGeometry(10, 230, 100, 40)

        self.modeFpsButton = QPushButton('4FPS', self)
        self.modeFpsButton.clicked.connect(self.rate)
        self.modeFpsButton.setGeometry(10, 280, 100, 40)

        self.modeAutoButton = QPushButton('Get offset', self)
        self.modeAutoButton.clicked.connect(self.commonOffset)
        self.modeAutoButton.setGeometry(570, 30, 100, 40)

        self.modeAutoButton = QPushButton('Get version', self)
        self.modeAutoButton.clicked.connect(self.sysVer)
        self.modeAutoButton.setGeometry(570, 80, 100, 40)

        self.evaluate = 1
        self.evaluateButton = QPushButton('Evaluate', self)
        self.evaluateButton.clicked.connect(self.setEvaluate)
        self.evaluateButton.setGeometry(570, 120, 100, 40)

        self.serial_l = QLabel(self)

        self.serial_l.move(580, 250)
        self.serial_l.resize(60, 30)
        self.serial_l.setStyleSheet(
            "QLabel{color:rgb(0,0,0,255);background-color: rgb(255,255,255);font-size:16px;font-weight:normal;font-family:Arial;}"
        )
        self.serial_l.setText("Serial:")

        self.serialList = SerialComboBox(self)
        self.serialList.setCurrentIndex(0)
        self.serialList.setStyleSheet(
            "border-width: 1px;border-style: solid;border-color: rgb(255, 170, 0);"
        )
        self.serialList.currentIndexChanged.connect(self.serialChange)
        # self.evaluateButton.setGeometry(570,140,100,40)
        self.serialList.move(580, 280)
        self.serialList.resize(120, 30)
        self.serialList.addItem("Please select serial device")

    def checkSerial(self):
        reply = QMessageBox.information(self, 'No serial',
                                        "Please select serial device",
                                        QMessageBox.Yes)

        # portlist = SerialComboBox().get_port_list(self)
        # self.userCom = None
        # if portlist is not None:
        #     for port in portlist:
        #         print("port ",port)
        #         self.userCom = port
        #     if self.userCom is not None :
        #         self.dataThread.openSerial(self.userCom)
        #     else:
        #         reply = QMessageBox.information(self, '没有设置串口', "请选择串口" , QMessageBox.Yes)

    def serialChange(self, i):
        print("serialChange", i, self.serialList.currentText())
        if i > 0:
            self.userCom = self.serialList.currentText()
            self.dataThread.initSerialPort(self.userCom)

    def ctrlOpen(self):
        print("self.userCom", self.userCom)
        if self.userCom is None:
            self.checkSerial()
            return

        if self.open_status == 1:
            print('start send C command 0')
            self.open_status = 0

            self.ctrlOpenButton.setText("Open")
            self.dataThread.sendData('CMDC\0')
        else:
            print('start send C command 1')
            self.open_status = 1

            self.ctrlOpenButton.setText("Close")
            self.dataThread.sendData('CMDC\1')

    def evaluate(self):
        if self.userCom is None:
            self.checkSerial()
            return
        global evaluate_mode
        if evaluate_mode == "operate":
            print('start send E command 0')
            evaluate_mode = "evaluate"
            self.EvaluateButton.setText("Evaluate")
            self.dataThread.sendData('CMDE\1')
        else:
            print('start send E command 1')
            evaluate_mode = "operate"
            self.EvaluateButton.setText("Operate")
            self.dataThread.sendData('CMDE\0')

    def ctrlSendone(self):
        if self.userCom is None:
            self.checkSerial()
            return
        print('send a frame')
        self.dataThread.sendData('CMDC\2')

    def modeManual(self):
        if self.userCom is None:
            self.checkSerial()
            return
        if self.mode == 1:
            self.getOnePicButton.setEnabled(True)
            self.modeManualButton.setText("Manual")
            self.dataThread.sendData('CMDM\0')
            self.mode = 0
            print('mode: manual')
        else:

            self.getOnePicButton.setEnabled(False)
            self.modeManualButton.setText("Auto")
            self.dataThread.sendData('CMDM\1')
            print('mode: auto')
            self.mode = 1

    def modeAuto(self):
        if self.userCom is None:
            self.checkSerial()
            return
        print('mode: auto')
        self.dataThread.sendData('CMDM\1')

    def rate(self):
        if self.userCom is None:
            self.checkSerial()
            return
        global btn_index
        btn_index = (btn_index + 1) % 5
        print('FPS:', strButton[btn_index])
        self.modeFpsButton.setText(strButton[btn_index])
        self.dataThread.sendData(strFpsCmd[btn_index])  #'CMDF\0')

    def objBody(self):
        if self.userCom is None:
            self.checkSerial()
            return
        if self.body == 1:
            self.body = 0
            print('obj: Object')
            self.modeObjButton.setText("Object")
            self.dataThread.sendData('CMDO\0')
        else:
            self.body = 1
            print('obj: Human Body')
            self.modeObjButton.setText("Body")
            self.dataThread.sendData('CMDO\1')

    def uiUpdate(self):
        global evaluate_mode
        print("update UI ", evaluate_mode)
        if evaluate_mode == "operate":
            self.EvaluateButton.setText("Operate")
        elif evaluate_mode == "evaluate":
            self.EvaluateButton.setText("Evaluate")
        else:
            self.EvaluateButton.setText("None")

    def cmdUpdate(self):
        if self.userCom is None:
            self.checkSerial()
            return
        print("get evaluate status and version\n")
        time.sleep(0.1)
        self.dataThread.sendData('CMDE\2')
        time.sleep(0.1)
        self.dataThread.sendData('CMDV\0')
        time.sleep(0.1)
        self.dataThread.sendData('CMDT\1')

    def commonOffset(self):
        if self.userCom is None:
            self.checkSerial()
            return
        self.dataThread.sendData('CMDT\1')
        print("Get commonOffset")

    def sysVer(self):
        if self.userCom is None:
            self.checkSerial()
            return
        self.dataThread.sendData('CMDV\1')
        print("Get firmware version and calibration version.")

    def setEvaluate(self):
        if self.userCom is None:
            self.checkSerial()
            return
        if self.evaluate == 1:
            self.evaluate = 0
            self.evaluateButton.setText("Operate")
            self.dataThread.sendData('CMDE\0')
        else:
            self.evaluate = 1
            self.evaluateButton.setText("Evaluate")
            self.dataThread.sendData('CMDE\1')

    def draw(self):
        global minHue
        global maxHue
        global maxHet
        global minHet
        global device_commonOffset
        global evaluate_mode
        if len(displayData) == 0:
            return
        font = QFont()
        color = QColor()
        font.setPointSize(self.fontSize)
        #font.setFamily("Microsoft YaHei")
        font.setLetterSpacing(QFont.AbsoluteSpacing, 0)
        index = 0
        lock.acquire()
        frame = displayData.pop(0)
        lock.release()
        p = QPainter(self.cameraBuffer)
        p.fillRect(0, 0, self.width, self.height + self.textLineHeight,
                   QBrush(QColor(Qt.black)))
        # draw camera
        color = QColor()
        cneter = 0.0
        if chip == "90640":
            if self.centerIndex == 0:
                centerIndex = 12 * 32 + 16
            for yIndex in range(int(self.height / self.pixelSize)):
                for xIndex in range(int(self.width / self.pixelSize)):
                    color.setHsvF(frame[index] / 360, 1.0, 1.0)
                    p.fillRect(xIndex * self.pixelSize,
                               yIndex * self.pixelSize, self.pixelSize,
                               self.pixelSize, QBrush(color))
                    index = index + 1
            cneter = round(
                mapValue(frame[self.centerIndex], minHue, maxHue, minHet,
                         maxHet), 1)
        elif chip == "90621":
            if self.centerIndex == 0 or self.centerIndex >= 64:
                self.centerIndex = 2 * 16 + 8

            cneter = round(
                mapValue(frame[self.centerIndex], minHue, maxHue, minHet,
                         maxHet), 1)
            for yIndex in range(int(self.height / self.pixelSize / 6)):
                for xIndex in range(int(self.width / self.pixelSize / 2)):
                    color.setHsvF(frame[index] / 360, 1.0, 1.0)
                    p.fillRect(xIndex * self.pixelSize * 2,
                               yIndex * self.pixelSize * 2 + 160,
                               self.pixelSize * 2, self.pixelSize * 2,
                               QBrush(color))
                    index = index + 1
        elif chip == "90641":
            if self.centerIndex == 0 or self.centerIndex >= 192:
                self.centerIndex = 6 * 16 + 8

            cneter = round(
                mapValue(frame[self.centerIndex], minHue, maxHue, minHet,
                         maxHet), 1)
            for yIndex in range(int(self.height / self.pixelSize / 2)):
                for xIndex in range(int(self.width / self.pixelSize / 2)):
                    color.setHsvF(frame[index] / 360, 1.0, 1.0)
                    p.fillRect(xIndex * self.pixelSize * 2,
                               yIndex * self.pixelSize * 2, self.pixelSize * 2,
                               self.pixelSize * 2, QBrush(color))
                    index = index + 1
        else:
            print("Dsiplay Error: can't detect any chip type!")

        self.cameraItem.setPixmap(self.cameraBuffer)
        self.frameCount = self.frameCount + 1

        # draw text
        p = QPainter(self.hetTextBuffer)
        p.fillRect(0, 0, self.width + 200, self.height + self.textLineHeight,
                   QBrush(QColor(Qt.black)))

        version = self.dataThread.getID()
        p.setPen(QColor(Qt.white))
        p.setFont(font)
        p.drawText(0, self.fontSize, " max:" + '{:.2f}'.format(maxHet))
        p.drawText(self.textInterval, self.fontSize,
                   " min:" + '{:.2f}'.format(minHet))
        p.drawText(self.textInterval * 2, self.fontSize,
                   "offset:" + '{:.2f}'.format(device_commonOffset))
        p.drawText(self.textInterval * 3, self.fontSize,
                   "mode:" + detect_status)
        p.drawText(self.textInterval * 4, self.fontSize, evaluate_mode)
        p.drawText(self.textInterval * 5, self.fontSize,
                   "ID:" + str(version[0]))
        p.drawText(self.textInterval * 6, self.fontSize,
                   "ver:" + str(version[1] & 0xff))
        p.drawText(self.textInterval * 7, self.fontSize, chip)
        self.hetTextItem.setPixmap(self.hetTextBuffer)
        cneter = round(
            mapValue(frame[self.centerIndex], minHue, maxHue, minHet, maxHet),
            1)
        centerText = "<font color=white>%s</font>"
        self.centerTextItem.setFont(font)
        self.centerTextItem.setHtml(centerText % (str(cneter) + "°"))
        # draw version text
        self.versionTextItem.setFont(font)
Beispiel #28
0
class ImageViewer(QGraphicsView, QObject):
    def __init__(self, parent=None):
        super(ImageViewer, self).__init__(parent)
        self.setRenderHints(QPainter.Antialiasing
                            | QPainter.SmoothPixmapTransform)
        #self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self._scene = ImageViewerScene(self)
        self.setScene(self._scene)
        self._create_grid()
        self._create_grid_lines()
        self._pixmap = None
        self._selection_mode = SELECTION_MODE.NONE

        # polygon selection
        _polygon_guide_line_pen = QPen(QtGui.QColor(235, 72, 40))
        _polygon_guide_line_pen.setWidth(2)
        _polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine)
        self._polygon_guide_line = QGraphicsLineItem()
        self._polygon_guide_line.setVisible(False)
        self._polygon_guide_line.setPen(_polygon_guide_line_pen)
        self._scene.addItem(self._polygon_guide_line)
        self._current_polygon = None
        # rectangle selection
        self._box_origin = QPoint()
        self._box_picker = QRubberBand(QRubberBand.Rectangle, self)

        # free selection
        self._current_free_path = None
        self._is_drawing = False
        self._last_point_drawn = QPoint()
        self._current_label = None

    @property
    def current_label(self):
        return self._current_label

    @current_label.setter
    def current_label(self, value):
        self._current_label = value

    @property
    def pixmap(self) -> ImagePixmap:
        return self._pixmap

    @pixmap.setter
    def pixmap(self, value: QPixmap):
        self.selection_mode = SELECTION_MODE.NONE
        self.resetTransform()
        if self.pixmap:
            self._scene.removeItem(self._pixmap)
        self.remove_annotations()
        self._pixmap = ImagePixmap()
        self._pixmap.setPixmap(value)
        self._pixmap.setOffset(-value.width() / 2, -value.height() / 2)
        self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation)
        self._pixmap.signals.hoverEnterEventSgn.connect(
            self.pixmap_hoverEnterEvent_slot)
        self._pixmap.signals.hoverLeaveEventSgn.connect(
            self.pixmap_hoverLeaveEvent_slot)
        self._pixmap.signals.hoverMoveEventSgn.connect(
            self.pixmap_hoverMoveEvent_slot)
        self._scene.addItem(self._pixmap)
        # rect=self._scene.addRect(QtCore.QRectF(0,0,100,100), QtGui.QPen(QtGui.QColor("red")))
        # rect.setZValue(1.0)
        self.fit_to_window()

    @property
    def selection_mode(self):
        return self._selection_mode

    @selection_mode.setter
    def selection_mode(self, value):
        self._polygon_guide_line.hide()
        self._current_polygon = None
        self._current_free_path = None
        self._is_drawing = value == SELECTION_MODE.FREE
        if value == SELECTION_MODE.NONE:
            self.enable_items(True)
        else:
            self.enable_items(False)
        self._selection_mode = value

    def remove_annotations(self):
        for item in self._scene.items():
            if isinstance(item, EditableBox):
                self._scene.removeItem(item)
            elif isinstance(item, EditablePolygon):
                item.delete_polygon()

    def remove_annotations_by_label(self, label_name):
        for item in self._scene.items():
            if isinstance(item, EditableBox):
                if item.label and item.label.name == label_name:
                    self._scene.removeItem(item)
            elif isinstance(item, EditablePolygon):
                if item.label and item.label.name == label_name:
                    item.delete_polygon()

    def enable_items(self, value):
        for item in self._scene.items():
            if isinstance(item, EditablePolygon) or isinstance(
                    item, EditableBox):
                item.setEnabled(value)

    def _create_grid(self):
        gridSize = 15
        backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2)
        #backgroundPixmap.fill(QtGui.QColor("white"))
        backgroundPixmap.fill(QtGui.QColor(20, 20, 20))
        #backgroundPixmap.fill(QtGui.QColor("powderblue"))
        painter = QtGui.QPainter(backgroundPixmap)
        #backgroundColor=QtGui.QColor("palegoldenrod")
        #backgroundColor=QtGui.QColor(237,237,237)
        backgroundColor = QtGui.QColor(0, 0, 0)
        painter.fillRect(0, 0, gridSize, gridSize, backgroundColor)
        painter.fillRect(gridSize, gridSize, gridSize, gridSize,
                         backgroundColor)
        painter.end()
        self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap))

    def _create_grid_lines(self):
        pen_color = QColor(255, 255, 255, 255)
        pen = QPen(pen_color)
        pen.setWidth(2)
        pen.setStyle(QtCore.Qt.DotLine)
        self.vline = QGraphicsLineItem()
        self.vline.setVisible(False)
        self.vline.setPen(pen)
        self.hline = QGraphicsLineItem()
        self.hline.setVisible(False)
        self.hline.setPen(pen)
        self._scene.addItem(self.vline)
        self._scene.addItem(self.hline)

    def wheelEvent(self, event: QWheelEvent):
        adj = (event.angleDelta().y() / 120) * 0.1
        self.scale(1 + adj, 1 + adj)

    def fit_to_window(self):
        """Fit image within view."""
        if not self.pixmap or not self._pixmap.pixmap():
            return
        #self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation)
        self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio)

    def show_guide_lines(self):
        if self.hline and self.vline:
            self.hline.show()
            self.vline.show()

    def hide_guide_lines(self):
        if self.hline and self.vline:
            self.hline.hide()
            self.vline.hide()

    def pixmap_hoverEnterEvent_slot(self):
        self.show_guide_lines()

    def pixmap_hoverLeaveEvent_slot(self):
        self.hide_guide_lines()

    def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y):
        bbox: QRect = self._pixmap.boundingRect()
        offset = QPointF(bbox.width() / 2, bbox.height() / 2)
        self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y())
        self.vline.setZValue(1)
        self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y)
        self.hline.setZValue(1)

    def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None:
        if self.selection_mode == SELECTION_MODE.BOX:
            if not self._box_origin.isNull():
                self._box_picker.setGeometry(
                    QRect(self._box_origin, evt.pos()).normalized())
        elif self.selection_mode == SELECTION_MODE.POLYGON:
            if self._current_polygon:
                if self._current_polygon.count > 0:
                    last_point: QPointF = self._current_polygon.last_point
                    self._polygon_guide_line.setZValue(1)
                    self._polygon_guide_line.show()
                    mouse_pos = self.mapToScene(evt.pos())
                    self._polygon_guide_line.setLine(last_point.x(),
                                                     last_point.y(),
                                                     mouse_pos.x(),
                                                     mouse_pos.y())
            else:
                self._polygon_guide_line.hide()

        elif self.selection_mode == SELECTION_MODE.FREE and evt.buttons(
        ) and QtCore.Qt.LeftButton:
            if self._current_free_path:
                painter: QPainterPath = self._current_free_path.path()
                self._last_point_drawn = self.mapToScene(evt.pos())
                painter.lineTo(self._last_point_drawn)
                self._current_free_path.setPath(painter)

        super(ImageViewer, self).mouseMoveEvent(evt)

    def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:

        if evt.buttons() == QtCore.Qt.LeftButton:
            if self.selection_mode == SELECTION_MODE.BOX:
                self.setDragMode(QGraphicsView.NoDrag)
                self._box_origin = evt.pos()
                self._box_picker.setGeometry(QRect(self._box_origin, QSize()))
                self._box_picker.show()

            elif self._selection_mode == SELECTION_MODE.POLYGON:
                pixmap_rect: QRectF = self._pixmap.boundingRect()
                new_point = self.mapToScene(evt.pos())
                # consider only the points intothe image
                if pixmap_rect.contains(new_point):
                    if self._current_polygon is None:
                        self._current_polygon = EditablePolygon()
                        self._current_polygon.signals.deleted.connect(
                            self.delete_polygon_slot)
                        self._scene.addItem(self._current_polygon)
                        self._current_polygon.addPoint(new_point)
                    else:
                        self._current_polygon.addPoint(new_point)

            elif self._selection_mode == SELECTION_MODE.FREE:
                # start drawing
                new_point = self.mapToScene(evt.pos())
                pixmap_rect: QRectF = self._pixmap.boundingRect()
                # consider only the points intothe image
                if pixmap_rect.contains(new_point):
                    self.setDragMode(QGraphicsView.NoDrag)
                    pen = QPen(QtGui.QColor(235, 72, 40))
                    pen.setWidth(10)
                    self._last_point_drawn = new_point
                    self._current_free_path = QGraphicsPathItem()
                    self._current_free_path.setOpacity(0.6)
                    self._current_free_path.setPen(pen)
                    painter = QPainterPath()
                    painter.moveTo(self._last_point_drawn)
                    self._current_free_path.setPath(painter)
                    self._scene.addItem(self._current_free_path)
        else:
            self.setDragMode(QGraphicsView.ScrollHandDrag)

        super(ImageViewer, self).mousePressEvent(evt)

    def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None:
        if evt.button() == QtCore.Qt.LeftButton:
            if self.selection_mode == SELECTION_MODE.BOX:
                roi: QRect = self._box_picker.geometry()
                roi: QRectF = self.mapToScene(roi).boundingRect()
                pixmap_rect = self._pixmap.boundingRect()
                self._box_picker.hide()
                if pixmap_rect == roi.united(pixmap_rect):
                    rect = EditableBox(roi)
                    rect.label = self.current_label
                    self._scene.addItem(rect)
                    self.selection_mode = SELECTION_MODE.NONE
                    self.setDragMode(QGraphicsView.ScrollHandDrag)

            elif self.selection_mode == SELECTION_MODE.FREE and self._current_free_path:
                # create polygon
                self._current_free_path: QGraphicsPathItem
                path_rect = self._current_free_path.boundingRect()
                pixmap_rect = self._pixmap.boundingRect()
                if pixmap_rect == path_rect.united(pixmap_rect):
                    path = self._current_free_path.path()
                    path_polygon = EditablePolygon()
                    path_polygon.label = self.current_label
                    self._scene.addItem(path_polygon)
                    for i in range(0, path.elementCount(), 10):
                        x, y = path.elementAt(i).x, path.elementAt(i).y
                        path_polygon.addPoint(QPointF(x, y))
                self._scene.removeItem(self._current_free_path)
                self.selection_mode = SELECTION_MODE.NONE
                self.setDragMode(QGraphicsView.ScrollHandDrag)

        super(ImageViewer, self).mouseReleaseEvent(evt)

    def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
        if self._current_polygon and event.key() == QtCore.Qt.Key_Space:
            points = self._current_polygon.points
            self._current_polygon.label = self.current_label
            self._current_polygon = None
            self.selection_mode = SELECTION_MODE.NONE
            self._polygon_guide_line.hide()
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).keyPressEvent(event)

    def delete_polygon_slot(self, polygon: EditablePolygon):
        self._current_polygon = None
        self.selection_mode = SELECTION_MODE.NONE
        self._polygon_guide_line.hide()
Beispiel #29
0
class painter(QGraphicsView):
    narrowRatio = int(sys.argv[4]) if len(sys.argv) >= 5 else 1
    useBlur = sys.argv[5] != "False" if len(sys.argv) >= 6 else True
    pixelSize = int(15 / narrowRatio)
    width = int(480 / narrowRatio)
    height = int(360 / narrowRatio)
    fontSize = int(30 / narrowRatio)
    anchorLineSize = int(100 / narrowRatio)
    ellipseRadius = int(8 / narrowRatio)
    textInterval = int(90 / narrowRatio)
    col = width / pixelSize
    line = height / pixelSize
    centerIndex = int(round(((line / 2 - 1) * col) + col / 2))
    frameCount = 0
    baseZValue = 0
    textLineHeight = fontSize + 10
    blurRaduis = 50  # Smoother improvement

    def __init__(self):
        super(painter, self).__init__()
        self.setFixedSize(self.width, self.height + self.textLineHeight)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scene = QGraphicsScene()
        self.setScene(self.scene)
        # center het text item
        self.centerTextItem = QGraphicsTextItem()
        self.centerTextItem.setPos(self.width / 2 - self.fontSize, 0)
        self.centerTextItem.setZValue(self.baseZValue + 1)
        self.scene.addItem(self.centerTextItem)
        # center anchor item
        centerX = self.width / 2
        centerY = self.height / 2
        self.ellipseItem = QGraphicsEllipseItem(0, 0, self.ellipseRadius * 2,
                                                self.ellipseRadius * 2)
        self.horLineItem = QGraphicsLineItem(0, 0, self.anchorLineSize, 0)
        self.verLineItem = QGraphicsLineItem(0, 0, 0, self.anchorLineSize)
        self.ellipseItem.setPos(centerX - self.ellipseRadius,
                                centerY - self.ellipseRadius)
        self.horLineItem.setPos(centerX - self.anchorLineSize / 2, centerY)
        self.verLineItem.setPos(centerX, centerY - self.anchorLineSize / 2)
        self.ellipseItem.setPen(QColor(Qt.white))
        self.horLineItem.setPen(QColor(Qt.white))
        self.verLineItem.setPen(QColor(Qt.white))
        self.ellipseItem.setZValue(self.baseZValue + 1)
        self.horLineItem.setZValue(self.baseZValue + 1)
        self.verLineItem.setZValue(self.baseZValue + 1)
        self.scene.addItem(self.ellipseItem)
        self.scene.addItem(self.horLineItem)
        self.scene.addItem(self.verLineItem)
        # camera item
        self.cameraBuffer = QPixmap(self.width,
                                    self.height + self.textLineHeight)
        self.cameraItem = QGraphicsPixmapItem()
        if self.useBlur:
            self.gusBlurEffect = QGraphicsBlurEffect()
            self.gusBlurEffect.setBlurRadius(self.blurRaduis)
            self.cameraItem.setGraphicsEffect(self.gusBlurEffect)
        self.cameraItem.setPos(0, 0)
        self.cameraItem.setZValue(self.baseZValue)
        self.scene.addItem(self.cameraItem)
        # het text item
        self.hetTextBuffer = QPixmap(self.width, self.textLineHeight)
        self.hetTextItem = QGraphicsPixmapItem()
        self.hetTextItem.setPos(0, self.height)
        self.hetTextItem.setZValue(self.baseZValue)
        self.scene.addItem(self.hetTextItem)

    def draw(self):
        if len(hetaData) == 0:
            return
        font = QFont()
        color = QColor()
        font.setPointSize(self.fontSize)
        font.setFamily("Microsoft YaHei")
        font.setLetterSpacing(QFont.AbsoluteSpacing, 0)
        index = 0
        lock.acquire()
        frame = hetaData.pop(0)
        lock.release()
        maxHet = frame["maxHet"]
        minHet = frame["minHet"]
        frame = frame["frame"]
        p = QPainter(self.cameraBuffer)
        p.fillRect(0, 0, self.width, self.height + self.textLineHeight,
                   QBrush(QColor(Qt.black)))
        # draw camera
        color = QColor()
        for yIndex in range(int(self.height / self.pixelSize)):
            for xIndex in range(int(self.width / self.pixelSize)):
                tempData = constrain(
                    mapValue(frame[index], minHet, maxHet, minHue, maxHue),
                    minHue, maxHue)
                color.setHsvF(tempData / 360, 1.0, 1.0)
                p.fillRect(xIndex * self.pixelSize, yIndex * self.pixelSize,
                           self.pixelSize, self.pixelSize, QBrush(color))
                index = index + 1
        self.cameraItem.setPixmap(self.cameraBuffer)
        # draw text
        p = QPainter(self.hetTextBuffer)
        p.fillRect(0, 0, self.width, self.height + self.textLineHeight,
                   QBrush(QColor(Qt.black)))
        hetDiff = maxHet - minHet
        bastNum = round(minHet)
        interval = round(hetDiff / 5)
        for i in range(5):
            hue = constrain(
                mapValue((bastNum + (i * interval)), minHet, maxHet, minHue,
                         maxHue), minHue, maxHue)
            color.setHsvF(hue / 360, 1.0, 1.0)
            p.setPen(color)
            p.setFont(font)
            p.drawText(i * self.textInterval, self.fontSize + 3,
                       str(bastNum + (i * interval)) + "°")
        self.hetTextItem.setPixmap(self.hetTextBuffer)
        # draw center het text
        cneter = round(frame[self.centerIndex], 1)
        centerText = "<font color=white>%s</font>"
        self.centerTextItem.setFont(font)
        self.centerTextItem.setHtml(centerText % (str(cneter) + "°"))
        self.frameCount = self.frameCount + 1
        print("picture->" + str(self.frameCount))
class BaseGraphic(object):
    def __init__(self, *args):
        super(BaseGraphic, self).__init__()
        self.label = args[3]
        self.parent = args[0]
        self.section_analyzer = self.parent.section_analyzer
        self.width = None
        self.height = None
        self.margin = None
        self.position = args[1], args[2]
        self.content_width = None
        self.content_height = None
        self.distance_pointer = None
        self.distance_label = None
        self.section_num = len(self.section_analyzer.section_list)
        self.section_distance = self.section_analyzer.section_distance
        self.length_multiplier = 100.0
        self.height_multiplier = 100.0
        self.line_extend = 20
        self.margin = 50
        self.material_legend = MaterialLegend(self)
        self._inited = False
        self.items = []
        self.add_title()

    def add_title(self):
        if self.label:
            title = QGraphicsSimpleTextItem(self.label)
            title.setPos(self.position[0] + self.margin, self.position[1] + self.line_extend)
            self.addToGroup(title)

    def addToGroup(self, item):
        self.items.append(item)

    def create_distance_pointer(self):
        self.distance_pointer = QGraphicsLineItem()
        pen = QPen()
        pen.setWidthF(1.0)
        pen.setStyle(Qt.DashDotLine)
        color = Color.create_qcolor_from_rgb_tuple_f((1,0,0))
        pen.setColor(color)
        self.distance_pointer.setPen(pen)
        self.distance_pointer.setZValue(1.0)
        self.addToGroup(self.distance_pointer)
        self.distance_label = QGraphicsSimpleTextItem()
        self.distance_label.setZValue(1.0)
        self.addToGroup(self.distance_label)


    def init_dimension(self):
        section_num = len(self.section_analyzer.section_list)
        section_distance = self.section_analyzer.section_distance
        self.content_width = section_num * section_distance * self.length_multiplier
        self.create_distance_pointer()
        self._inited = True

    def update_graph_size(self):
        if self.content_height and self.content_width:
            self.width = self.content_width + self.margin * 2
            self.height = self.content_height + self.margin * 2
        # bounding_rect.setWidth(self.width)
        # bounding_rect.setHeight(self.height)

    def set_distance_pointer(self, distance):
        if self._inited:
            x1 = self.position[0] + self.margin + distance * self.length_multiplier
            y1 = self.position[1]
            x2 = x1
            y2 = y1 + self.height
            self.distance_pointer.setLine(x1, y1, x2, y2)
            self.distance_label.setText("%.2f" % distance)
            self.distance_label.setPos(x2,y2)
        pass

    @staticmethod
    def set_rect_fill(*args):
        if args[0] == 0: #surface color mode
            rect = args[1]
            color = args[2]
            qcolor = Color.create_qcolor_from_rgb_tuple_f(color)
            brush = QBrush(qcolor)
            rect.setBrush(brush)

    def create_legend(self):
        x = self.position[0] + self.width
        y = self.position[1]
        self.material_legend.create_material_legend(x, y)
        for item in self.material_legend.graphic_items:
            self.addToGroup(item)
class SliceNucleicAcidPartItem(QAbstractPartItem):
    """Parent should be either a SliceRootItem, or an AssemblyItem.

    Invariant: keys in _empty_helix_hash = range(_nrows) x range(_ncols)
    where x is the cartesian product.

    Attributes:
        active_virtual_helix_item (cadnano.views.sliceview.virtualhelixitem.SliceVirtualHelixItem): Description
        resize_handle_group (ResizeHandleGroup): handles for dragging and resizing
        griditem (GridItem): Description
        outline (QGraphicsRectItem): Description
        prexover_manager (PreXoverManager): Description
        scale_factor (float): Description
    """
    _RADIUS = styles.SLICE_HELIX_RADIUS
    _BOUNDING_RECT_PADDING = 80

    def __init__(self, model_part_instance, viewroot, parent=None):
        """Summary

        Args:
            model_part_instance (TYPE): Description
            viewroot (TYPE): Description
            parent (None, optional): Description
        """
        super(SliceNucleicAcidPartItem, self).__init__(model_part_instance,
                                                       viewroot, parent)

        self.shortest_path_start = None
        self.neighbor_map = dict()
        self.coordinates_to_vhid = dict()
        self.coordinates_to_xy = dict()
        self._last_hovered_item = None
        self._highlighted_path = []
        self.spa_start_vhi = None
        self.last_mouse_position = None

        self._translated_x = 0.0
        self._translated_y = 0.0

        self._getActiveTool = viewroot.manager.activeToolGetter
        m_p = self._model_part
        self._controller = NucleicAcidPartItemController(self, m_p)
        self.scale_factor = self._RADIUS / m_p.radius()
        self.inverse_scale_factor = m_p.radius() / self._RADIUS
        self.active_virtual_helix_item = None
        self.prexover_manager = PreXoverManager(self)
        self.hide()  # hide while until after attemptResize() to avoid flicker
        self._rect = QRectF(0., 0., 1000., 1000.)  # set this to a token value
        self.boundRectToModel()
        self.setPen(getNoPen())
        self.setRect(self._rect)
        self.setAcceptHoverEvents(True)

        self.shortest_path_add_mode = False

        # Cache of VHs that were active as of last call to activeSliceChanged
        # If None, all slices 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)
        self.outline = outline = QGraphicsRectItem(self)
        o_rect = self._configureOutline(outline)
        outline.setFlag(QGraphicsItem.ItemStacksBehindParent)
        outline.setZValue(styles.ZDESELECTOR)
        model_color = m_p.getColor()
        self.outline.setPen(getPenObj(model_color, _DEFAULT_WIDTH))

        self.model_bounds_hint = QGraphicsRectItem(self)
        self.model_bounds_hint.setBrush(getBrushObj(model_color, alpha=12))
        self.model_bounds_hint.setPen(getNoPen())

        self.resize_handle_group = ResizeHandleGroup(
            o_rect,
            _HANDLE_SIZE,
            model_color,
            True,
            HandleType.TOP | HandleType.BOTTOM | HandleType.LEFT
            | HandleType.RIGHT | HandleType.TOP_LEFT | HandleType.TOP_RIGHT
            | HandleType.BOTTOM_LEFT | HandleType.BOTTOM_RIGHT,
            self,
            show_coords=True)

        self.griditem = GridItem(self, self._model_props['grid_type'])
        self.griditem.setZValue(1)
        self.resize_handle_group.setZValue(2)

        self.x_axis_line = QGraphicsLineItem(0, 0, self._RADIUS, 0, self)
        self.x_axis_line.setPen(getPenObj('#cc0000', _DEFAULT_WIDTH))
        self.x_axis_line.setZValue(styles.ZAXIS)
        self.y_axis_line = QGraphicsLineItem(0, 0, 0, -self._RADIUS, self)
        self.y_axis_line.setPen(getPenObj('#007200', _DEFAULT_WIDTH))
        self.y_axis_line.setZValue(styles.ZAXIS)

        # select upon creation
        for part in m_p.document().children():
            if part is m_p:
                part.setSelected(True)
            else:
                part.setSelected(False)
        self.show()

    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def partActiveVirtualHelixChangedSlot(self, part, id_num):
        """Summary

        Args:
            part (NucleicAcidPart): Description
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
        """
        vhi = self._virtual_helix_item_hash.get(id_num)
        self.setActiveVirtualHelixItem(vhi)
        self.setPreXoverItemsVisible(vhi)

    # end def

    def partActiveBaseInfoSlot(self, part, info):
        """Summary

        Args:
            part (TYPE): Description
            info (TYPE): Description

        Args:
            TYPE: Description
        """
        pxom = self.prexover_manager
        pxom.deactivateNeighbors()
        if info and info is not None:
            id_num, is_fwd, idx, _ = info
            pxom.activateNeighbors(id_num, is_fwd, idx)

    # end def

    def partPropertyChangedSlot(self, model_part, property_key, new_value):
        """Summary

        Args:
            model_part (Part): The model part
            property_key (TYPE): Description
            new_value (TYPE): Description

        Args:
            TYPE: Description
        """
        if self._model_part == model_part:
            self._model_props[property_key] = new_value
            if property_key == 'color':
                self.outline.setPen(getPenObj(new_value, _DEFAULT_WIDTH))
                for vhi in self._virtual_helix_item_hash.values():
                    vhi.updateAppearance()
                self.resize_handle_group.setPens(getPenObj(new_value, 0))
            elif property_key == 'is_visible':
                if new_value:
                    self.show()
                else:
                    self.hide()
            elif property_key == 'grid_type':
                self.griditem.setGridType(new_value)

    # end def

    def partRemovedSlot(self, sender):
        """docstring for partRemovedSlot

        Args:
            sender (obj): Model object that emitted the signal.
        """
        self.parentItem().removePartItem(self)

        scene = self.scene()

        scene.removeItem(self)

        self._model_part = None
        self._mod_circ = None

        self._controller.disconnectSignals()
        self._controller = None
        self.resize_handle_group.removeHandles()
        self.griditem = None

    # end def

    def partVirtualHelicesTranslatedSlot(self, sender, vh_set, left_overs,
                                         do_deselect):
        """
        left_overs are neighbors that need updating due to changes

        Args:
            sender (obj): Model object that emitted the signal.
            vh_set (TYPE): Description
            left_overs (TYPE): Description
            do_deselect (TYPE): Description
        """
        if do_deselect:
            tool = self._getActiveTool()
            if tool.methodPrefix() == "selectTool":
                if tool.isSelectionActive():
                    # tool.deselectItems()
                    tool.modelClear()

        # 1. move everything that moved
        for id_num in vh_set:
            vhi = self._virtual_helix_item_hash[id_num]
            vhi.updatePosition()
        # 2. now redraw what makes sense to be redrawn
        for id_num in vh_set:
            vhi = self._virtual_helix_item_hash[id_num]
            self._refreshVirtualHelixItemGizmos(id_num, vhi)
        for id_num in left_overs:
            vhi = self._virtual_helix_item_hash[id_num]
            self._refreshVirtualHelixItemGizmos(id_num, vhi)

        # 0. clear PreXovers:
        # self.prexover_manager.hideGroups()
        # if self.active_virtual_helix_item is not None:
        #     self.active_virtual_helix_item.deactivate()
        #     self.active_virtual_helix_item = None
        avhi = self.active_virtual_helix_item
        self.setPreXoverItemsVisible(avhi)
        self.enlargeRectToFit()

    # end def

    def _refreshVirtualHelixItemGizmos(self, id_num, vhi):
        """Update props and appearance of self & recent neighbors. Ultimately
        triggered by a partVirtualHelicesTranslatedSignal.

        Args:
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            vhi (cadnano.views.sliceview.virtualhelixitem.SliceVirtualHelixItem): the item associated with id_num
        """
        neighbors = vhi.cnModel().getProperty('neighbors')
        neighbors = literal_eval(neighbors)
        vhi.beginAddWedgeGizmos()
        for nvh in neighbors:
            nvhi = self._virtual_helix_item_hash.get(nvh, False)
            if nvhi:
                vhi.setWedgeGizmo(nvh, nvhi)
        # end for
        vhi.endAddWedgeGizmos()

    # end def

    def partVirtualHelixPropertyChangedSlot(self, sender, id_num,
                                            virtual_helix, keys, values):
        """Summary

        Args:
            sender (obj): Model object that emitted the signal.
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            keys (tuple): keys that changed
            values (tuple): new values for each key that changed

        Args:
            TYPE: Description
        """
        if self._model_part == sender:
            vh_i = self._virtual_helix_item_hash[id_num]
            vh_i.virtualHelixPropertyChangedSlot(keys, values)

    # end def

    def partVirtualHelixAddedSlot(self, sender, id_num, virtual_helix,
                                  neighbors):
        """Summary

        Args:
            sender (obj): Model object that emitted the signal.
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            neighbors (TYPE): Description

        Args:
            TYPE: Description
        """
        vhi = SliceVirtualHelixItem(virtual_helix, self)
        self._virtual_helix_item_hash[id_num] = vhi
        self._refreshVirtualHelixItemGizmos(id_num, vhi)
        for neighbor_id in neighbors:
            nvhi = self._virtual_helix_item_hash.get(neighbor_id, False)
            if nvhi:
                self._refreshVirtualHelixItemGizmos(neighbor_id, nvhi)
        self.enlargeRectToFit()

        position = sender.locationQt(id_num=id_num,
                                     scale_factor=self.scale_factor)
        coordinates = ShortestPathHelper.findClosestPoint(
            position=position, point_map=self.coordinates_to_xy)

        assert id_num not in self.coordinates_to_vhid.values()

        self.coordinates_to_vhid[coordinates] = id_num

        assert len(self.coordinates_to_vhid.keys()) == len(
            set(self.coordinates_to_vhid.keys()))
        assert len(self.coordinates_to_vhid.values()) == len(
            set(self.coordinates_to_vhid.values()))

    # end def

    def partVirtualHelixRemovingSlot(self, sender, id_num, virtual_helix,
                                     neighbors):
        """Summary

        Args:
            sender (obj): Model object that emitted the signal.
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            neighbors (TYPE): Description

        Args:
            TYPE: Description
        """
        tm = self._viewroot.manager
        tm.resetTools()
        self.removeVirtualHelixItem(id_num)
        for neighbor_id in neighbors:
            nvhi = self._virtual_helix_item_hash[neighbor_id]
            self._refreshVirtualHelixItemGizmos(neighbor_id, nvhi)

        for coordinates, current_id in self.coordinates_to_vhid.items():
            if current_id == id_num:
                del self.coordinates_to_vhid[coordinates]
                break

        assert id_num not in self.coordinates_to_vhid.values()
        assert len(self.coordinates_to_vhid.keys()) == len(
            set(self.coordinates_to_vhid.keys()))
        assert len(self.coordinates_to_vhid.values()) == len(
            set(self.coordinates_to_vhid.values()))

    # end def

    def partSelectedChangedSlot(self, model_part, is_selected):
        """Set this Z to front, and return other Zs to default.

        Args:
            model_part (Part): The model part
            is_selected (TYPE): Description
        """
        if is_selected:
            # self._drag_handle.resetAppearance(_SELECTED_COLOR, _SELECTED_WIDTH, _SELECTED_ALPHA)
            self.setZValue(styles.ZPARTITEM + 1)
        else:
            # self._drag_handle.resetAppearance(self.modelColor(), _DEFAULT_WIDTH, _DEFAULT_ALPHA)
            self.setZValue(styles.ZPARTITEM)

    # end def

    def partVirtualHelicesSelectedSlot(self, sender, vh_set, is_adding):
        """is_adding (bool): adding (True) virtual helices to a selection
        or removing (False)

        Args:
            sender (obj): Model object that emitted the signal.
            vh_set (TYPE): Description
            is_adding (TYPE): Description
        """
        select_tool = self._viewroot.select_tool
        if is_adding:
            select_tool.selection_set.update(vh_set)
            select_tool.setPartItem(self)
            select_tool.getSelectionBoundingRect()
        else:
            select_tool.deselectSet(vh_set)

    # end def

    def partDocumentSettingChangedSlot(self, part, key, value):
        """Summary

        Args:
            part (TYPE): Description
            key (TYPE): Description
            value (TYPE): Description

        Args:
            TYPE: Description

        Raises:
            ValueError: Description
        """
        if key == 'grid':
            if value == 'lines and points':
                self.griditem.setDrawlines(True)
            elif value == 'points':
                self.griditem.setDrawlines(False)
            elif value == 'circles':
                pass  # self.griditem.setGridAppearance(False)
            else:
                raise ValueError("unknown grid styling")

    ### ACCESSORS ###
    def boundingRect(self):
        """Summary

        Args:
            TYPE: Description
        """
        return self._rect

    # end def

    def modelColor(self):
        """Summary

        Args:
            TYPE: Description
        """
        return self._model_props['color']

    # end def

    def window(self):
        """Summary

        Args:
            TYPE: Description
        """
        return self.parentItem().window()

    # end def

    def setActiveVirtualHelixItem(self, new_active_vhi):
        """Summary

        Args:
            new_active_vhi (TYPE): Description

        """
        current_vhi = self.active_virtual_helix_item
        if new_active_vhi != current_vhi:
            if current_vhi is not None:
                current_vhi.deactivate()
            if new_active_vhi is not None:
                new_active_vhi.activate()
            self.active_virtual_helix_item = new_active_vhi

    # end def

    def setPreXoverItemsVisible(self, virtual_helix_item):
        """
        self._pre_xover_items list references prexovers parented to other
        PathHelices such that only the activeHelix maintains the list of
        visible prexovers

        Args:
            virtual_helix_item (cadnano.views.sliceview.virtualhelixitem.SliceVirtualHelixItem): Description
        """
        vhi = virtual_helix_item
        pxom = self.prexover_manager
        if vhi is None:
            pxom.hideGroups()
            return

        part = self.part()
        info = part.active_base_info
        if info:
            id_num, is_fwd, idx, to_vh_id_num = info
            per_neighbor_hits, pairs = part.potentialCrossoverMap(id_num, idx)
            pxom.activateVirtualHelix(virtual_helix_item, idx,
                                      per_neighbor_hits, pairs)

    # end def

    def removeVirtualHelixItem(self, id_num):
        """Summary

        Args:
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.

        Args:
            TYPE: Description
        """
        vhi = self._virtual_helix_item_hash[id_num]
        if vhi == self.active_virtual_helix_item:
            self.active_virtual_helix_item = None
        vhi.virtualHelixRemovedSlot()
        del self._virtual_helix_item_hash[id_num]

        # When any VH is removed, turn SPA mode off
        self.shortest_path_add_mode = False
        self.shortest_path_start = None

    # end def

    def reconfigureRect(self,
                        top_left,
                        bottom_right,
                        finish=False,
                        padding=80):
        """Reconfigures the rectangle that is the document.

        Args:
            top_left (tuple): A tuple corresponding to the x-y coordinates of
            top left corner of the document

            bottom_right (tuple): A tuple corresponding to the x-y coordinates
            of the bottom left corner of the document

        Returns:
            tuple: tuple of point tuples representing the top_left and
            bottom_right as reconfigured with padding
        """
        rect = self._rect
        ptTL = QPointF(
            *self.padTL(padding, *top_left)) if top_left else rect.topLeft()
        ptBR = QPointF(*self.padBR(
            padding, *bottom_right)) if bottom_right else rect.bottomRight()
        self._rect = QRectF(ptTL, ptBR)
        self.setRect(self._rect)
        self._configureOutline(self.outline)
        self.griditem.updateGrid()
        return self.outline.rect()

    # end def

    def padTL(self, padding, xTL, yTL):
        return xTL + padding, yTL + padding

    # end def

    def padBR(self, padding, xBR, yBR):
        return xBR - padding, yBR - padding

    # end def

    def enlargeRectToFit(self):
        """Enlarges Part Rectangle to fit the model bounds.

        This should be called when adding a SliceVirtualHelixItem.  This
        method enlarges the rectangle to ensure that it fits the design.
        This method needs to check the model size to do this, but also takes
        into account any expansions the user has made to the rectangle as to
        not shrink the rectangle after the user has expanded it.

        :rtype: None
        """
        padding = self._BOUNDING_RECT_PADDING

        model_left, model_top, model_right, model_bottom = self.getModelMinBounds(
        )
        rect_left, rect_right, rect_bottom, rect_top = self.bounds()

        xTL = min(rect_left, model_left) - padding
        xBR = max(rect_right, model_right) + padding
        yTL = min(rect_top, model_top) - padding
        yBR = max(rect_bottom, model_bottom) + padding
        new_outline_rect = self.reconfigureRect(top_left=(xTL, yTL),
                                                bottom_right=(xBR, yBR))
        self.resize_handle_group.alignHandles(new_outline_rect)
        # self.grab_cornerTL.alignPos(*top_left)
        # self.grab_cornerBR.alignPos(*bottom_right)

    ### PRIVATE SUPPORT METHODS ###
    def _configureOutline(self, outline):
        """Adjusts `outline` size with default padding.

        Args:
            outline (TYPE): Description

        Returns:
            o_rect (QRect): `outline` rect adjusted by _BOUNDING_RECT_PADDING
        """
        _p = self._BOUNDING_RECT_PADDING
        o_rect = self.rect().adjusted(-_p, -_p, _p, _p)
        outline.setRect(o_rect)
        return o_rect

    # end def

    def boundRectToModel(self):
        """Update the size of the rectangle corresponding to the grid to
        the size of the model or a minimum size (whichever is greater).

        :rtype: None
        """
        xTL, yTL, xBR, yBR = self.getModelMinBounds()
        self._rect = QRectF(QPointF(xTL, yTL), QPointF(xBR, yBR))

    # end def

    def getModelMinBounds(self, handle_type=None):
        """Bounds in form of Qt scaled from model

        Args:
            Tuple (top_left, bottom_right)

        :rtype: Tuple where
        """
        xLL, yLL, xUR, yUR = self.part().boundDimensions(self.scale_factor)
        # return xLL, -yUR, xUR, -yLL
        r = self._RADIUS
        return xLL - r, -yUR - r, xUR + r, -yLL + r

    # end def

    def bounds(self):
        """x_low, x_high, y_low, y_high
        """
        rect = self._rect
        return (rect.left(), rect.right(), rect.bottom(), rect.top())

    ### PUBLIC SUPPORT METHODS ###
    def setLastHoveredItem(self, gridpoint_item):
        """Stores the last self-reported griditem to be hovered.

        Args:
            griditem (GridItem): the hoveree
        """
        self._last_hovered_item = gridpoint_item

    def setModifyState(self, bool_val):
        """Hides the mod_rect when modify state disabled.

        Args:
            bool_val (boolean): what the modifystate should be set to.
        """
        self._can_show_mod_circ = bool_val
        if bool_val is False:
            self._mod_circ.hide()

    # end def

    def showModelMinBoundsHint(self, handle_type, show=True):
        """Shows QGraphicsRectItem reflecting current model bounds.
        ResizeHandleGroup should toggle this when resizing.

        Args:
            status_str (str): Description to display in status bar.
        """
        m_b_h = self.model_bounds_hint
        if show:
            xTL, yTL, xBR, yBR = self.getModelMinBounds()
            m_b_h.setRect(QRectF(QPointF(xTL, yTL), QPointF(xBR, yBR)))
            m_b_h.show()
        else:
            m_b_h.hide()

    def updateStatusBar(self, status_str):
        """Shows status_str in the MainWindow's status bar.

        Args:
            status_str (str): Description to display in status bar.
        """
        pass  # disabled for now.
        # self.window().statusBar().showMessage(status_str, timeout)

    # end def

    def zoomToFit(self):
        """Ask the view to zoom to fit.
        """
        thescene = self.scene()
        theview = thescene.views()[0]
        theview.zoomToFit()

    # end def

    ### EVENT HANDLERS ###
    def mousePressEvent(self, event):
        """Handler for user mouse press.

        Args:
            event (QGraphicsSceneMouseEvent): Contains item, scene, and screen
            coordinates of the the event, and previous event.

        Args:
            event (QMouseEvent): contains parameters that describe a mouse event.
        """
        if event.button() == Qt.RightButton:
            return
        part = self._model_part
        part.setSelected(True)
        if self.isMovable():
            return QGraphicsItem.mousePressEvent(self, event)
        tool = self._getActiveTool()
        if tool.FILTER_NAME not in part.document().filter_set:
            return
        tool_method_name = tool.methodPrefix() + "MousePress"
        if tool_method_name == 'createToolMousePress':
            return
        elif hasattr(self, tool_method_name):
            getattr(self, tool_method_name)(tool, event)
        else:
            event.setaccepted(False)
            QGraphicsItem.mousePressEvent(self, event)

    # end def

    def hoverMoveEvent(self, event):
        self.last_mouse_position = self.translateEventCoordinates(event)
        tool = self._getActiveTool()
        tool_method_name = tool.methodPrefix() + "HoverMove"
        if hasattr(self, tool_method_name):
            getattr(self, tool_method_name)(tool, event)
        else:
            event.setAccepted(False)
            QGraphicsItem.hoverMoveEvent(self, event)

    # def hoverLeaveEvent(self, event):
    #     pass
    # tool = self._getActiveTool()
    # tool.hideLineItem()

    def getModelPos(self, pos):
        """Y-axis is inverted in Qt +y === DOWN

        Args:
            pos (TYPE): Description
        """
        sf = self.scale_factor
        x, y = pos.x() / sf, -1.0 * pos.y() / sf
        return x, y

    # end def

    def getVirtualHelixItem(self, id_num):
        """Summary

        Args:
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.

        Returns:
            TYPE: Description
        """
        return self._virtual_helix_item_hash.get(id_num)

    # end def

    def keyPressEvent(self, event):
        is_alt = bool(event.modifiers() & Qt.AltModifier)
        if event.key() == Qt.Key_Escape:
            self._setShortestPathStart(None)
            self.removeAllCreateHints()
            if self._inPointItem(self.last_mouse_position,
                                 self.getLastHoveredCoordinates()):
                self.highlightOneGridPoint(self.getLastHoveredCoordinates())
        elif is_alt and self.shortest_path_add_mode is True:
            if self._inPointItem(self.last_mouse_position,
                                 self.getLastHoveredCoordinates()):
                x, y = self.coordinates_to_xy.get(
                    self.getLastHoveredCoordinates())
                self._preview_spa((x, y))
        elif is_alt:
            if self._inPointItem(self.last_mouse_position,
                                 self.getLastHoveredCoordinates()):
                self.highlightOneGridPoint(self.getLastHoveredCoordinates(),
                                           styles.SPA_START_HINT_COLOR)

    # end def

    def keyReleaseEvent(self, event):
        is_alt = bool(event.modifiers() & Qt.AltModifier)
        if not is_alt:
            self.removeAllCreateHints()
            if self._inPointItem(self.last_mouse_position,
                                 self.getLastHoveredCoordinates()):
                self.highlightOneGridPoint(self.getLastHoveredCoordinates())

    # end def

    def createToolMousePress(self, tool, event, alt_event=None):
        """Creates individual or groups of VHs in Part on user input.
        Shift modifier enables multi-helix addition.

        Args:
            event (TYPE): Description
            alt_event (None, optional): Description
        """
        # Abort if a VH already exists here
        position = self.translateEventCoordinates(event)

        # 1. get point in model coordinates:
        part = self._model_part
        if alt_event is None:
            pt = tool.eventToPosition(self, event)
        else:
            pt = alt_event.pos()

        if pt is None:
            tool.deactivate()
            return QGraphicsItem.mousePressEvent(self, event)

        part_pt_tuple = self.getModelPos(pt)
        modifiers = event.modifiers()

        is_spa_mode = modifiers == Qt.AltModifier
        last_added_spa_vhi_id = self._handleShortestPathMousePress(
            tool=tool, position=position, is_spa_mode=is_spa_mode)
        if last_added_spa_vhi_id is not None:
            return

        row, column = self.getLastHoveredCoordinates()
        parity = self._getCoordinateParity(row, column)

        part.createVirtualHelix(x=part_pt_tuple[0],
                                y=part_pt_tuple[1],
                                parity=parity)
        id_num = part.getVirtualHelixAtPoint(part_pt_tuple)
        vhi = self._virtual_helix_item_hash[id_num]
        tool.setVirtualHelixItem(vhi)
        tool.startCreation()

        if is_spa_mode:
            self._highlightSpaVH(id_num)

    # end def

    def _getCoordinateParity(self, row, column):
        if self.griditem.grid_type is GridType.HONEYCOMB:
            return 0 if HoneycombDnaPart.isOddParity(row=row,
                                                     column=column) else 1
        elif self.griditem.grid_type is GridType.SQUARE:
            return 0 if SquareDnaPart.isEvenParity(row=row,
                                                   column=column) else 1
        else:
            return None

    def _handleShortestPathMousePress(self, tool, position, is_spa_mode):
        """
        Handles logic for determining if SPA mode should be activated or
        continued.

        Args:
            tool ():
            position (tuple):  the xy coordinates of the mouse press
            is_spa_mode (bool):  whether or not this event is a SPA event

        Returns:
            True if nothing needs to be done by the caller (i.e. this method
            and its callees added VHs as necessary, False otherwise
        """
        if is_spa_mode:
            # Complete the path
            if self.shortest_path_start is not None:
                last_vhi_id = self.createToolShortestPath(
                    tool=tool, start=self.shortest_path_start, end=position)
                if last_vhi_id is not None:
                    self._setShortestPathStart(position)
                    self._highlightSpaVH(last_vhi_id)
                    return last_vhi_id
            # Initialize SPA
            else:
                self._setShortestPathStart(position)
        else:
            self._setShortestPathStart(None)

    def _setShortestPathStart(self, position):
        # TODO[NF]:  Docstring
        if position is not None:
            self.shortest_path_add_mode = True
            self.shortest_path_start = position
        else:
            self.shortest_path_add_mode = False
            self.shortest_path_start = None
            self._highlightSpaVH(None)

    def _highlightSpaVH(self, vh_id):
        # TODO[NF]:  Docstring
        if self.spa_start_vhi:
            self.spa_start_vhi.setBrush(getNoBrush())

        if vh_id is None:
            self.spa_start_vhi = None
        else:
            self.spa_start_vhi = self._virtual_helix_item_hash[vh_id]
            self.spa_start_vhi.setBrush(
                getBrushObj(styles.SPA_START_HINT_COLOR, alpha=32))

    # end def

    def createToolShortestPath(self, tool, start, end):
        """
        Handle the creation of VHIs for SPA mode.

        Args:
            tool ():
            start (tuple):  the x-y coordinates of the start point
            end (tuple):  the x-y coordinates of the end point

        Returns:
            The ID of the last VHI created
        """
        path = ShortestPathHelper.shortestPathXY(
            start=start,
            end=end,
            neighbor_map=self.neighbor_map,
            vh_set=self.coordinates_to_vhid.keys(),
            point_map=self.coordinates_to_xy,
            grid_type=self.griditem.grid_type,
            scale_factor=self.inverse_scale_factor,
            radius=self._RADIUS)

        # Abort and exit SPA if there is no path from start to end
        if path == []:
            self.shortest_path_start = None
            self.shortest_path_add_mode = False
            return None
        else:
            x_list, y_list, parity_list = zip(*path)
            id_numbers = self._model_part.batchCreateVirtualHelices(
                x_list=x_list, y_list=y_list, parity=parity_list)
            for id_number in id_numbers:
                vhi = self._virtual_helix_item_hash[id_number]
                tool.setVirtualHelixItem(vhi)
                tool.startCreation()
            return id_number

    # end def

    def createToolHoverMove(self, tool, event):
        """Summary

        Args:
            tool (TYPE): Description
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        event_xy = self.translateEventCoordinates(event)
        event_coord = ShortestPathHelper.findClosestPoint(
            event_xy, self.coordinates_to_xy)
        is_alt = True if event.modifiers() & Qt.AltModifier else False
        self.last_mouse_position = event_xy

        # Un-highlight GridItems if necessary by calling createToolHoverLeave
        if len(self._highlighted_path) > 1 or (
                self._highlighted_path
                and self._highlighted_path[0] != event_coord):
            self.createToolHoverLeave(tool=tool, event=event)

        # Highlight GridItems if alt is being held down
        if is_alt and self.shortest_path_add_mode and self._inPointItem(
                event_xy, event_coord):
            self._preview_spa(event_xy)
        else:
            point_item = self.coordinates_to_xy.get(event_coord)

            if point_item is not None and self._inPointItem(
                    event_xy, event_coord) and is_alt:
                self.highlightOneGridPoint(self.getLastHoveredCoordinates(),
                                           styles.SPA_START_HINT_COLOR)
            elif point_item is not None and self._inPointItem(
                    event_xy, event_coord) and not is_alt:
                part = self._model_part
                next_idnums = (part._getNewIdNum(0), part._getNewIdNum(1))
                self.griditem.showCreateHint(event_coord,
                                             next_idnums=next_idnums)

                self._highlighted_path.append(event_coord)

        tool.hoverMoveEvent(self, event)
        return QGraphicsItem.hoverMoveEvent(self, event)

    # end def

    def _inPointItem(self, event_xy, event_coord):
        """
        Determine if x-y coordinates are inside the given GridPoint.

        Args:
            event_xy (tuple):  the x-y coordinates corresponding to the
                position of the mouse
            event_coord (tuple):  the i-j coordinates corresponding to the
                location of the GridPoint

        Returns:
            True if the mouse is in the given GridPoint, False otherwise
        """
        if event_xy is None or event_coord is None:
            return False

        point_x, point_y = self.coordinates_to_xy.get(event_coord)
        event_x, event_y = event_xy

        return (point_x - event_x)**2 + (point_y -
                                         event_y)**2 <= self._RADIUS**2

    # end def

    def _preview_spa(self, event_xy):
        """
        Highlight and add VH ID numbers to the GridPoints that the SPA would
        use.

        Args:
            event_xy (tuple):  the x-y coordinates corresponding to the
                position of the mouse

        Returns:
            None
        """
        part = self._model_part
        start_coord = self.shortest_path_start
        end_coord = event_xy
        self._highlighted_path = ShortestPathHelper.shortestPathAStar(
            start=start_coord,
            end=end_coord,
            neighbor_map=self.neighbor_map,
            vh_set=self.coordinates_to_vhid.keys(),
            point_map=self.coordinates_to_xy)
        even_id = part._getNewIdNum(0)
        odd_id = part._getNewIdNum(1)
        for coord in self._highlighted_path:
            is_odd = self.griditem.showCreateHint(coord,
                                                  next_idnums=(even_id,
                                                               odd_id))
            if is_odd is True:
                odd_id += 2
            elif is_odd is False:
                even_id += 2

    # end def

    def createToolHoverLeave(self, tool, event):
        self.removeAllCreateHints()

    def selectToolMousePress(self, tool, event):
        """
        Args:
            tool (TYPE): Description
            event (TYPE): Description
        """
        tool.setPartItem(self)
        pt = tool.eventToPosition(self, event)
        part_pt_tuple = self.getModelPos(pt)
        part = self._model_part
        if part.isVirtualHelixNearPoint(part_pt_tuple):
            id_num = part.getVirtualHelixAtPoint(part_pt_tuple)
            if id_num is not None:
                pass
                # loc = part.getCoordinate(id_num, 0)
                # print("VirtualHelix #{} at ({:.3f}, {:.3f})".format(id_num, loc[0], loc[1]))
            else:
                # tool.deselectItems()
                tool.modelClear()
        else:
            # tool.deselectItems()
            tool.modelClear()
        return QGraphicsItem.mousePressEvent(self, event)

    # end def

    def setNeighborMap(self, neighbor_map):
        """
        Update the internal mapping of coordinates to their neighbors.

        Args:
            neighbor_map (dict):  the new mapping of coordinates to their
                neighbors

        Returns:
            None
        """
        assert isinstance(neighbor_map, dict)
        self.neighbor_map = neighbor_map

    # end def

    def setPointMap(self, point_map):
        """
        Update the internal mapping of coordinates to x-y positions.

        Args:
            point_map (dict):  the new mapping of coordinates to their x-y
                position

        Returns:
            None

        """
        assert isinstance(point_map, dict)
        self.coordinates_to_xy = point_map

    # end def

    def updateTranslatedOffsets(self, delta_x, delta_y):
        """
        Update the values used to calculate translational offsets.

        Args:
            delta_x (float):  the new value for which we've translated in the x
                direction
            delta_y (float):  the new value for which we've translated in the y
                direction

        Returns:
            None
        """
        assert isinstance(delta_x, float)
        assert isinstance(delta_y, float)

        self._translated_x = delta_x
        self._translated_y = delta_y

    # end def

    def translateEventCoordinates(self, event):
        """
        Given an event, return the x-y coordinates of the event accounting for
        any translations that may have happened

        Args:
            event (MousePressEvent):  the event for which x-y coordinates should
                be returned

        Returns:
            A tuple of x-y coordinates of the event
        """
        assert isinstance(event, QGraphicsSceneEvent)
        return event.scenePos().x() - self._translated_x, event.scenePos().y(
        ) - self._translated_y

    # end def

    def removeAllCreateHints(self):
        """
        Remove the create hints from each currently hinted GridItem.

        Iterates over all coordinates in self._highlighted_path.

        Returns:
            None
        """
        for coord in self._highlighted_path:
            self.griditem.showCreateHint(coord, show_hint=False)
        self._highlighted_path = []

    # end def

    def highlightOneGridPoint(self, coordinates, color=None):
        """
        Add a hint to one GridPoint.

        Args:
            coordinates (tuple):  the row-column coordinates of the gridPoint to
                be highlighted
            color ():  the color that the gridPoint should be changed to

        Returns:
            None
        """
        if coordinates is None:
            return

        assert isinstance(coordinates, tuple) and len(coordinates) is 2
        assert isinstance(coordinates[0], int) and isinstance(
            coordinates[1], int)

        if self.coordinates_to_xy.get(coordinates) is not None:
            part = self._model_part
            next_idnums = (part._getNewIdNum(0), part._getNewIdNum(1))
            self.griditem.showCreateHint(coordinates,
                                         next_idnums=next_idnums,
                                         color=color)
            self._highlighted_path.append(coordinates)

    # end def

    def getLastHoveredCoordinates(self):
        """
        Get the row and column corresponding to the GridPoint that was most
        recently hovered over.

        This accounts for the fact that the rows are inverted (i.e. the result
        returned by this method will match the coordinate system stored in this
        class' internal records of coordinates)

        Returns:
            A tuple corresponding to the row and column of the most recently
                hovered GridPoint.

        """
        if self._last_hovered_item:
            row, column = self._last_hovered_item.coord()
            return -row, column
Beispiel #32
0
class ASCGraphicsView(QGraphicsView):
    scale_signal = pyqtSignal('float', 'float')
    set_focus_point_signal = pyqtSignal('int', 'int', 'int')
    set_focus_point_percent_signal = pyqtSignal('float', 'float', 'float')
    move_focus_point_signal = pyqtSignal('int', 'int', 'int')
    # x, y, z, BRUSH_TYPE, BRUSH_SIZE, ERASE
    paint_anno_on_point_signal = pyqtSignal('int', 'int', 'int', 'int', 'int',
                                            'bool', 'bool')

    def __init__(self, parent=None):
        super(ASCGraphicsView, self).__init__(parent)
        self.scene = QGraphicsScene(self)

        self.raw_img_item = QGraphicsPixmapItem()
        self.raw_img_item.setZValue(0)
        self.anno_img_item = QGraphicsPixmapItem()
        self.anno_img_item.setZValue(1)

        self.cross_bar_v_line_item = QGraphicsLineItem()
        self.cross_bar_h_line_item = QGraphicsLineItem()
        self.cross_bar_v_line_item.setZValue(100)
        self.cross_bar_h_line_item.setZValue(100)
        self.cross_bar_v_line_item.setPen(
            QPen(Qt.blue, 0, Qt.DotLine, Qt.FlatCap, Qt.RoundJoin))
        self.cross_bar_h_line_item.setPen(
            QPen(Qt.blue, 0, Qt.DotLine, Qt.FlatCap, Qt.RoundJoin))

        self.paint_brush_circle_item = QGraphicsEllipseItem()
        self.paint_brush_rect_item = QGraphicsPolygonItem()
        self.paint_brush_circle_item.setZValue(10)
        self.paint_brush_rect_item.setZValue(11)
        self.paint_brush_circle_item.setVisible(False)
        self.paint_brush_rect_item.setVisible(False)
        self.paint_brush_circle_item.setPen(
            QPen(Qt.red, 0, Qt.DotLine, Qt.FlatCap, Qt.RoundJoin))
        self.paint_brush_rect_item.setPen(
            QPen(Qt.red, 0, Qt.DotLine, Qt.FlatCap, Qt.RoundJoin))

        self.scene.addItem(self.raw_img_item)
        self.scene.addItem(self.anno_img_item)
        self.scene.addItem(self.cross_bar_v_line_item)
        self.scene.addItem(self.cross_bar_h_line_item)
        self.scene.addItem(self.paint_brush_circle_item)
        self.scene.addItem(self.paint_brush_rect_item)

        self.setScene(self.scene)
        self.setViewport(QOpenGLWidget())

        self._last_button_press = Qt.NoButton
        self._last_pos_middle_button = None
        self._last_pos_right_button = None

        self._brush_stats = {'type': BRUSH_TYPE_NO_BRUSH, 'size': 5}

        self.setResizeAnchor(QGraphicsView.AnchorViewCenter)

        self.slice_scroll_bar = None
        self.image_size = None
        self.is_valid = False

    def clear(self):
        """before loading new image"""
        self._last_pos_middle_button = None
        self._last_pos_right_button = None
        self._last_button_press = Qt.NoButton
        self.raw_img_item.setPixmap(QPixmap())
        self.anno_img_item.setPixmap(QPixmap())
        self.paint_brush_circle_item.setVisible(False)
        self.paint_brush_rect_item.setVisible(False)
        self.image_size = None
        self.is_valid = False

    def init_view(self, image_size):
        """after loading new image"""
        self.is_valid = True
        self.image_size = image_size
        self.slice_scroll_bar = self.parent().findChild(
            QScrollBar,
            self.objectName()[0] + 'SliceScrollBar')

        trans_mat = item2scene_transform[self.objectName()[0]]
        self.raw_img_item.setTransform(trans_mat)
        self.anno_img_item.setTransform(trans_mat)
        self.cross_bar_v_line_item.setTransform(trans_mat)
        self.cross_bar_h_line_item.setTransform(trans_mat)
        self.paint_brush_rect_item.setTransform(trans_mat)
        self.paint_brush_circle_item.setTransform(trans_mat)

        self.fitInView(self.raw_img_item, Qt.KeepAspectRatio)
        self.paint_brush_circle_item.setVisible(False)
        self.paint_brush_rect_item.setVisible(False)

    @property
    def brush_stats(self):
        return self._brush_stats

    @brush_stats.setter
    def brush_stats(self, stats_tuple):
        b_type, size = stats_tuple
        if b_type in [
                BRUSH_TYPE_NO_BRUSH, BRUSH_TYPE_CIRCLE_BRUSH,
                BRUSH_TYPE_RECT_BRUSH
        ]:
            self._brush_stats['type'] = b_type
            self._brush_stats['size'] = size
        if b_type != BRUSH_TYPE_NO_BRUSH:
            self.setMouseTracking(True)
        else:
            self.setMouseTracking(False)

    def update_brush_preview(self, x, y, out_of_sight=False):
        if not self.is_valid or self.brush_stats[
                'type'] == BRUSH_TYPE_NO_BRUSH or out_of_sight:
            self.paint_brush_rect_item.setVisible(False)
            self.paint_brush_circle_item.setVisible(False)
            return

        center = self.anno_img_item.mapFromScene(self.mapToScene(x, y))

        start_x = self.raw_img_item.boundingRect().topLeft().x()
        start_y = self.raw_img_item.boundingRect().topLeft().y()
        end_x = self.raw_img_item.boundingRect().bottomRight().x()
        end_y = self.raw_img_item.boundingRect().bottomRight().y()
        center.setX(min(max(start_x, center.x()), end_x) + 0.5)
        center.setY(min(max(start_y, center.y()), end_y) + 0.5)
        top_left_x = int(center.x() - self.brush_stats['size'] / 2)
        top_left_y = int(center.y() - self.brush_stats['size'] / 2)
        rect = QRectF(top_left_x, top_left_y, self.brush_stats['size'],
                      self.brush_stats['size'])
        if self.brush_stats['type'] == BRUSH_TYPE_CIRCLE_BRUSH:
            self.paint_brush_rect_item.setVisible(False)
            self.paint_brush_circle_item.setVisible(True)
            self.paint_brush_circle_item.setRect(rect)
        if self.brush_stats['type'] == BRUSH_TYPE_RECT_BRUSH:
            self.paint_brush_rect_item.setVisible(True)
            self.paint_brush_circle_item.setVisible(False)
            self.paint_brush_rect_item.setPolygon(QPolygonF(rect))

    def anno_paint(self, x, y, erase=False, new_step=False):
        pos_on_item = self.raw_img_item.mapFromScene(self.mapToScene(x, y))
        if self.objectName() == 'aGraphicsView':
            paint_point = [pos_on_item.y(), pos_on_item.x(), 999999]
        if self.objectName() == 'sGraphicsView':
            paint_point = [999999, pos_on_item.y(), pos_on_item.x()]
        if self.objectName() == 'cGraphicsView':
            paint_point = [pos_on_item.y(), 999999, pos_on_item.x()]
        self.paint_anno_on_point_signal.emit(math.floor(paint_point[0]),
                                             math.floor(paint_point[1]),
                                             math.floor(paint_point[2]),
                                             self.brush_stats['type'],
                                             self.brush_stats['size'], erase,
                                             new_step)

    @pyqtSlot('int')
    def on_slice_scroll_bar_changed(self, value):
        if not self.is_valid:
            return
        ratios = [-1, -1, -1]
        ratio = (value - self.slice_scroll_bar.minimum()) / \
                (self.slice_scroll_bar.maximum() - self.slice_scroll_bar.minimum())
        if self.objectName() == 'aGraphicsView':
            ratios[2] = ratio
        if self.objectName() == 'sGraphicsView':
            ratios[0] = ratio
        if self.objectName() == 'cGraphicsView':
            ratios[1] = ratio
        self.set_focus_point_percent_signal.emit(ratios[0], ratios[1],
                                                 ratios[2])

    @pyqtSlot('int', 'int')
    def set_brush_stats(self, b_type, size):
        self.brush_stats = [b_type, size]

    @pyqtSlot('int', 'int', 'int')
    def set_cross_bar(self, x, y, z):
        if self.objectName() == 'aGraphicsView':
            cross_bar_x = y
            cross_bar_y = x
            slice_bar_ratio = z / self.image_size[2]
        if self.objectName() == 'sGraphicsView':
            cross_bar_x = z
            cross_bar_y = y
            slice_bar_ratio = x / self.image_size[0]
        if self.objectName() == 'cGraphicsView':
            cross_bar_x = z
            cross_bar_y = x
            slice_bar_ratio = y / self.image_size[1]
        # cross line in voxel center
        cross_bar_x = cross_bar_x + 0.5
        cross_bar_y = cross_bar_y + 0.5
        start_x = self.raw_img_item.boundingRect().topLeft().x()
        start_y = self.raw_img_item.boundingRect().topLeft().y()
        end_x = self.raw_img_item.boundingRect().bottomRight().x()
        end_y = self.raw_img_item.boundingRect().bottomRight().y()
        self.cross_bar_v_line_item.setLine(cross_bar_x, start_y, cross_bar_x,
                                           end_y)
        self.cross_bar_h_line_item.setLine(start_x, cross_bar_y, end_x,
                                           cross_bar_y)

        slice_bar_value = round(slice_bar_ratio * (self.slice_scroll_bar.maximum() - self.slice_scroll_bar.minimum())) \
                          + self.slice_scroll_bar.minimum()
        self.slice_scroll_bar.setValue(slice_bar_value)

    def mousePressEvent(self, event: QtGui.QMouseEvent):
        self._last_button_press = event.button()

        if self.brush_stats['type'] == BRUSH_TYPE_NO_BRUSH:
            if event.button() == Qt.LeftButton:
                item_coord_pos = self.raw_img_item.mapFromScene(
                    self.mapToScene(event.pos()))
                if self.objectName() == 'aGraphicsView':
                    new_focus_point = [
                        item_coord_pos.y(),
                        item_coord_pos.x(), 999999
                    ]
                if self.objectName() == 'sGraphicsView':
                    new_focus_point = [
                        999999, item_coord_pos.y(),
                        item_coord_pos.x()
                    ]
                if self.objectName() == 'cGraphicsView':
                    new_focus_point = [
                        item_coord_pos.y(), 999999,
                        item_coord_pos.x()
                    ]
                self.set_focus_point_signal.emit(
                    math.floor(new_focus_point[0]),
                    math.floor(new_focus_point[1]),
                    math.floor(new_focus_point[2]))
            elif event.button() == Qt.MiddleButton:
                self._last_pos_middle_button = event.pos()
                self.setCursor(Qt.ClosedHandCursor)
            elif event.button() == Qt.RightButton:
                self._last_pos_right_button = event.pos()
            else:
                super(ASCGraphicsView, self).mousePressEvent(event)
        if self.brush_stats['type'] in [
                BRUSH_TYPE_CIRCLE_BRUSH, BRUSH_TYPE_RECT_BRUSH
        ]:
            if event.button() == Qt.LeftButton:
                self.anno_paint(event.x(),
                                event.y(),
                                erase=False,
                                new_step=True)
            elif event.button() == Qt.MiddleButton:
                self._last_pos_middle_button = event.pos()
                self.setCursor(Qt.ClosedHandCursor)
            elif event.button() == Qt.RightButton:
                self.anno_paint(event.x(),
                                event.y(),
                                erase=True,
                                new_step=True)

    def mouseMoveEvent(self, event: QtGui.QMouseEvent):
        if self.brush_stats['type'] == BRUSH_TYPE_NO_BRUSH:
            if self._last_button_press == Qt.LeftButton:
                item_coord_pos = self.raw_img_item.mapFromScene(
                    self.mapToScene(event.pos()))
                if self.objectName() == 'aGraphicsView':
                    new_focus_point = [
                        item_coord_pos.y(),
                        item_coord_pos.x(), 999999
                    ]
                if self.objectName() == 'sGraphicsView':
                    new_focus_point = [
                        999999, item_coord_pos.y(),
                        item_coord_pos.x()
                    ]
                if self.objectName() == 'cGraphicsView':
                    new_focus_point = [
                        item_coord_pos.y(), 999999,
                        item_coord_pos.x()
                    ]
                self.set_focus_point_signal.emit(
                    math.floor(new_focus_point[0]),
                    math.floor(new_focus_point[1]),
                    math.floor(new_focus_point[2]))
            elif self._last_button_press == Qt.MiddleButton:
                delta_x = event.x() - self._last_pos_middle_button.x()
                delta_y = event.y() - self._last_pos_middle_button.y()
                self.horizontalScrollBar().setValue(
                    self.horizontalScrollBar().value() - delta_x)
                self.verticalScrollBar().setValue(
                    self.verticalScrollBar().value() - delta_y)
                self._last_pos_middle_button = event.pos()
            elif self._last_button_press == Qt.RightButton:
                delta = event.pos().y() - self._last_pos_right_button.y()
                scale = 1 - float(delta) / float(self.size().height())
                self.scale_signal.emit(scale, scale)
                self._last_pos_right_button = event.pos()
            else:
                super(ASCGraphicsView, self).mouseMoveEvent(event)
        if self.brush_stats['type'] in [
                BRUSH_TYPE_CIRCLE_BRUSH, BRUSH_TYPE_RECT_BRUSH
        ]:
            self.update_brush_preview(event.x(), event.y())
            if self._last_button_press == Qt.LeftButton:
                self.anno_paint(event.x(),
                                event.y(),
                                erase=False,
                                new_step=False)
            elif self._last_button_press == Qt.MiddleButton:
                delta_x = event.x() - self._last_pos_middle_button.x()
                delta_y = event.y() - self._last_pos_middle_button.y()
                self.horizontalScrollBar().setValue(
                    self.horizontalScrollBar().value() - delta_x)
                self.verticalScrollBar().setValue(
                    self.verticalScrollBar().value() - delta_y)
                self._last_pos_middle_button = event.pos()
            elif self._last_button_press == Qt.RightButton:
                self.anno_paint(event.x(),
                                event.y(),
                                erase=True,
                                new_step=False)

    def mouseReleaseEvent(self, event: QtGui.QMouseEvent):
        self._last_button_press = Qt.NoButton

        if self.brush_stats['type'] == BRUSH_TYPE_NO_BRUSH:
            if event.button() == Qt.MiddleButton:
                self.setCursor(Qt.ArrowCursor)
        if self.brush_stats['type'] in [
                BRUSH_TYPE_CIRCLE_BRUSH, BRUSH_TYPE_RECT_BRUSH
        ]:
            if event.button() == Qt.LeftButton:
                pass
            elif event.button() == Qt.RightButton:
                pass
        else:
            super(ASCGraphicsView, self).mouseReleaseEvent(event)

    def wheelEvent(self, event: QtGui.QWheelEvent):
        # super(ASCGraphicsView, self).wheelEvent(event)
        if self.objectName() == 'aGraphicsView':
            if event.angleDelta().y() > 0:
                self.move_focus_point_signal.emit(0, 0, -1)
            elif event.angleDelta().y() < 0:
                self.move_focus_point_signal.emit(0, 0, 1)
        if self.objectName() == 'sGraphicsView':
            if event.angleDelta().y() > 0:
                self.move_focus_point_signal.emit(-1, 0, 0)
            elif event.angleDelta().y() < 0:
                self.move_focus_point_signal.emit(1, 0, 0)
        if self.objectName() == 'cGraphicsView':
            if event.angleDelta().y() > 0:
                self.move_focus_point_signal.emit(0, -1, 0)
            elif event.angleDelta().y() < 0:
                self.move_focus_point_signal.emit(0, 1, 0)

    def leaveEvent(self, event: QtCore.QEvent):
        self._last_button_press = Qt.NoButton
        if self.brush_stats['type'] == BRUSH_TYPE_NO_BRUSH:
            super(ASCGraphicsView, self).leaveEvent(event)
        else:
            self.update_brush_preview(0, 0, out_of_sight=True)
Beispiel #33
0
class ChartView(QChartView):

    def __init__(self, *args, **kwargs):
        super(ChartView, self).__init__(*args, **kwargs)
        self.resize(800, 600)
        self.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
        self.initChart()

        # 提示widget
        self.toolTipWidget = GraphicsProxyWidget(self._chart)

        # line 宽度需要调整
        self.lineItem = QGraphicsLineItem(self._chart)
        pen = QPen(Qt.gray)
        self.lineItem.setPen(pen)
        self.lineItem.setZValue(998)
        self.lineItem.hide()

        # 一些固定计算,减少mouseMoveEvent中的计算量
        # 获取x和y轴的最小最大值
        axisX, axisY = self._chart.axisX(), self._chart.axisY()
        self.category_len = len(axisX.categories())
        self.min_x, self.max_x = -0.5, self.category_len - 0.5
        self.min_y, self.max_y = axisY.min(), axisY.max()
        # 坐标系中左上角顶点
        self.point_top = self._chart.mapToPosition(
            QPointF(self.min_x, self.max_y))

    def mouseMoveEvent(self, event):
        super(ChartView, self).mouseMoveEvent(event)
        pos = event.pos()
        # 把鼠标位置所在点转换为对应的xy值
        x = self._chart.mapToValue(pos).x()
        y = self._chart.mapToValue(pos).y()
        index = round(x)
        # 得到在坐标系中的所有bar的类型和点
        serie = self._chart.series()[0]
        bars = [(bar, bar.at(index))
                for bar in serie.barSets() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
        if bars:
            right_top = self._chart.mapToPosition(
                QPointF(self.max_x, self.max_y))
            # 等分距离比例
            step_x = round(
                (right_top.x() - self.point_top.x()) / self.category_len)
            posx = self._chart.mapToPosition(QPointF(x, self.min_y))
            self.lineItem.setLine(posx.x(), self.point_top.y(),
                                  posx.x(), posx.y())
            self.lineItem.show()
            try:
                title = self.categories[index]
            except:
                title = ""
            t_width = self.toolTipWidget.width()
            t_height = self.toolTipWidget.height()
            # 如果鼠标位置离右侧的距离小于tip宽度
            x = pos.x() - t_width if self.width() - \
                pos.x() - 20 < t_width else pos.x()
            # 如果鼠标位置离底部的高度小于tip高度
            y = pos.y() - t_height if self.height() - \
                pos.y() - 20 < t_height else pos.y()
            self.toolTipWidget.show(
                title, bars, QPoint(x, y))
        else:
            self.toolTipWidget.hide()
            self.lineItem.hide()

    def handleMarkerClicked(self):
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        bar = marker.barset()
        if not bar:
            return
        # bar透明度
        brush = bar.brush()
        color = brush.color()
        alpha = 0.0 if color.alphaF() == 1.0 else 1.0
        color.setAlphaF(alpha)
        brush.setColor(color)
        bar.setBrush(brush)
        # marker
        brush = marker.labelBrush()
        color = brush.color()
        alpha = 0.4 if color.alphaF() == 1.0 else 1.0
        # 设置label的透明度
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setLabelBrush(brush)
        # 设置marker的透明度
        brush = marker.brush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setBrush(brush)

    def handleMarkerHovered(self, status):
        # 设置bar的画笔宽度
        marker = self.sender()  # 信号发送者
        if not marker:
            return
        bar = marker.barset()
        if not bar:
            return
        pen = bar.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if status else -1))
        bar.setPen(pen)

    def handleBarHoverd(self, status, index):
        # 设置bar的画笔宽度
        bar = self.sender()  # 信号发送者
        pen = bar.pen()
        if not pen:
            return
        pen.setWidth(pen.width() + (1 if status else -1))
        bar.setPen(pen)

    def initChart(self):
        self._chart = QChart(title="柱状图堆叠")
        self._chart.setAcceptHoverEvents(True)
        # Series动画
        self._chart.setAnimationOptions(QChart.SeriesAnimations)
        self.categories = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
        # names = ["邮件营销", "联盟广告", "视频广告", "直接访问", "搜索引擎"]
        names = ["邮件营销", ]
        series = QBarSeries(self._chart)
        for name in names:
            bar = QBarSet(name)
            # 随机数据
            for _ in range(7):
                bar.append(randint(0, 10))
                
            series.append(bar)
            bar.hovered.connect(self.handleBarHoverd)  # 鼠标悬停
        self._chart.addSeries(series)
        self._chart.createDefaultAxes()  # 创建默认的轴
        # x轴
        axis_x = QBarCategoryAxis(self._chart)
        axis_x.append(self.categories)
        self._chart.setAxisX(axis_x, series)
        # chart的图例
        legend = self._chart.legend()
        legend.setVisible(True)
        # 遍历图例上的标记并绑定信号
        for marker in legend.markers():
            # 点击事件
            marker.clicked.connect(self.handleMarkerClicked)
            # 鼠标悬停事件
            marker.hovered.connect(self.handleMarkerHovered)
        self.setChart(self._chart)
Beispiel #34
0
    def mainFunc(self, playing, scriptList, scriptPos):

        if self.inputImg is not None:
            self.outputImg = self.inputImg

            pass_ = 0

            fx = 1.0
            fy = 1.0
            offset_x = 0.0
            offset_y = 0.0
            for i in range(scriptPos):
                widget = scriptList[i][1]
                if widget.objectName() == 'resize':
                    fx *= widget.dblFx.value()
                    fy *= widget.dblFx.value()

                pass_ += widget.pass_
                for item in widget.graphicsItems:
                    try:
                        # ~ print('item.rect() ', item.rect())
                        if fx != 1.0:
                            item.setScale(1 / fx)
                        # ~ if fy != 1.0:
                        # ~ item.setHeight(item.height()/fy)
                        if offset_x:
                            item.setX((item.x() + offset_x) / fx)
                            # ~ item.setX( (item.x() + offset_x)/1.0 )
                            # ~ print('offset_x: ', offset_x, '  fx: ', fx)
                        if offset_y:
                            item.setY((item.y() + offset_y) / fy)
                            # ~ item.setY( (item.y() + offset_y)/1.0 )
                        self.graphicsView.scene().addItem(item)
                        item.setZValue(1)
                    except RuntimeError as e:
                        print(time.time(),
                              'Runtime Error show_graphics_items mainFunc')
                        print(e)

                if widget.objectName() == 'circles':
                    if widget.chkCrop.isChecked():
                        try:
                            circle = widget.graphicsItems[0]
                            # ~ circle = item
                            offset_x = circle.x()
                            offset_x = circle.rect().left()
                            offset_y = circle.y()
                            offset_y = circle.rect().top()
                            # ~ print('offer setter ', offset_x)
                        except IndexError as e:
                            print(e)
            if self.chkShowPass.isChecked():
                # ~ h = self.graphicsView.scene().height()
                # ~ w = self.graphicsView.scene().width()
                h = self.inputImg.shape[0]
                w = self.inputImg.shape[1]

                # ~ sfh = float(h)/float(self.graphicsView.minimumHeight())
                # ~ sfw = float(w)/float(self.graphicsView.minimumWidth())
                # ~ sf = min(sfh,sfw)

                outlineRect = QGraphicsRectItem(0, 0, w, h)
                outlineLine = QGraphicsLineItem(0, h, w, 0)

                font = QFont("DejaVu Sans", 45)

                if pass_ == scriptPos:
                    # the overall result is pass
                    text = 'PASS'
                    pen = QPen(QColor(0, 255, 0), 8)  #green for pass
                    brush = QBrush(QColor(0, 255, 0))

                else:
                    # overall result is fail
                    text = 'FAIL'
                    pen = QPen(QColor(255, 0, 0), 8)  #red for fail
                    brush = QBrush(QColor(255, 0, 0))

                    outlineLine.setPen(pen)
                    self.graphicsView.scene().addItem(outlineLine)
                    outlineLine.setZValue(1)

                outlineRect.setPen(pen)
                self.graphicsView.scene().addItem(outlineRect)
                textItem = QGraphicsSimpleTextItem(text, )
                textItem.setFont(font)
                textItem.setBrush(brush)
                textItem.setPos(w / 10.0, h / 10.0)
                self.graphicsView.scene().addItem(textItem)
                textItem.setZValue(1)
Beispiel #35
0
class IMUChartView(QChartView, BaseGraph):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        # Render on OpenGL
        self.setViewport(QOpenGLWidget())

        self.xvalues = {}

        self.chart = QChart()

        self.setChart(self.chart)
        self.chart.legend().setVisible(True)
        self.chart.legend().setAlignment(Qt.AlignTop)
        self.ncurves = 0
        self.setRenderHint(QPainter.Antialiasing)

        # Cursor (with chart as parent)
        self.cursor = QGraphicsLineItem(self.chart)

        self.cursor.setZValue(100.0)
        # self.scene().addItem(self.cursor)

        # Selection features
        # self.setRubberBand(QChartView.HorizontalRubberBand)
        self.selectionBand = QRubberBand(QRubberBand.Rectangle, self)
        self.selecting = False
        self.initialClick = QPoint()

        # Track mouse
        self.setMouseTracking(True)

        self.labelValue = QLabel(self)
        self.labelValue.setStyleSheet(
            "background-color: rgba(255,255,255,75%); color: black;")
        self.labelValue.setAlignment(Qt.AlignCenter)
        self.labelValue.setMargin(5)
        self.labelValue.setVisible(False)

        self.build_style()

        self.selection_start_time = None
        self.selection_stop_time = None
        self.cursor_time = None

    def build_style(self):
        self.setStyleSheet("QLabel{color:blue;}")
        self.chart.setTheme(QChart.ChartThemeBlueCerulean)
        self.setBackgroundBrush(QBrush(Qt.darkGray))
        self.chart.setPlotAreaBackgroundBrush(QBrush(Qt.black))
        self.chart.setPlotAreaBackgroundVisible(True)

    def save_as_png(self, file_path):
        pixmap = self.grab()

        child = self.findChild(QOpenGLWidget)

        painter = QPainter(pixmap)
        if child is not None:
            d = child.mapToGlobal(QPoint()) - self.mapToGlobal(QPoint())
            painter.setCompositionMode(QPainter.CompositionMode_SourceAtop)
            painter.drawImage(d, child.grabFramebuffer())

        painter.end()
        pixmap.save(file_path, 'PNG')

    #  def closeEvent(self, event):
    #     self.aboutToClose.emit(self)

    @staticmethod
    def decimate(xdata, ydata):
        # assert(len(xdata) == len(ydata))

        # Decimate only if we have too much data
        decimate_factor = len(xdata) / 100000.0
        # decimate_factor = 1.0

        if decimate_factor > 1.0:
            decimate_factor = int(np.floor(decimate_factor))
            # print('decimate factor', decimate_factor)

            x = np.ndarray(int(len(xdata) / decimate_factor), dtype=np.float64)
            y = np.ndarray(int(len(ydata) / decimate_factor), dtype=np.float64)
            # for i in range(len(x)):
            for i, _ in enumerate(x):
                index = i * decimate_factor
                # assert(index < len(xdata))
                x[i] = xdata[index]
                y[i] = ydata[index]
                if x[i] < x[0]:
                    print('timestamp error', x[i], x[0])
            return x, y
        else:
            return xdata, ydata

    @pyqtSlot(float, float)
    def axis_range_changed(self, min_value, max_value):
        # print('axis_range_changed', min, max)
        for axis in self.chart.axes():
            axis.applyNiceNumbers()

    def update_axes(self):

        # Get and remove all axes
        for axis in self.chart.axes():
            self.chart.removeAxis(axis)

        # Create new axes
        # Create axis X
        axisx = QDateTimeAxis()
        axisx.setTickCount(5)
        axisx.setFormat("dd MMM yyyy hh:mm:ss")
        axisx.setTitleText("Date")
        self.chart.addAxis(axisx, Qt.AlignBottom)

        # Create axis Y
        axisY = QValueAxis()
        axisY.setTickCount(5)
        axisY.setLabelFormat("%.3f")
        axisY.setTitleText("Values")
        self.chart.addAxis(axisY, Qt.AlignLeft)
        # axisY.rangeChanged.connect(self.axis_range_changed)

        ymin = None
        ymax = None

        # Attach axes to series, find min-max
        for series in self.chart.series():
            series.attachAxis(axisx)
            series.attachAxis(axisY)
            vect = series.pointsVector()
            # for i in range(len(vect)):
            for i, _ in enumerate(vect):
                if ymin is None:
                    ymin = vect[i].y()
                    ymax = vect[i].y()
                else:
                    ymin = min(ymin, vect[i].y())
                    ymax = max(ymax, vect[i].y())

        # Update range
        # print('min max', ymin, ymax)
        if ymin is not None:
            axisY.setRange(ymin, ymax)

        # Make the X,Y axis more readable
        # axisx.applyNiceNumbers()
        # axisY.applyNiceNumbers()

    def add_data(self, xdata, ydata, color=None, legend_text=None):
        curve = QLineSeries()
        pen = curve.pen()
        if color is not None:
            pen.setColor(color)
        pen.setWidthF(1.5)

        curve.setPen(pen)
        # curve.setPointsVisible(True)

        # curve.setUseOpenGL(True)

        self.total_samples = max(self.total_samples, len(xdata))

        # Decimate
        xdecimated, ydecimated = self.decimate(xdata, ydata)

        # Data must be in ms since epoch
        # curve.append(self.series_to_polyline(xdecimated * 1000.0, ydecimated))
        # self.reftime = datetime.datetime.fromtimestamp(xdecimated[0])

        # if len(xdecimated) > 0:
        #    xdecimated = xdecimated - xdecimated[0]

        xdecimated *= 1000  # No decimal expected
        points = []
        for i, _ in enumerate(xdecimated):
            # TODO hack
            # curve.append(QPointF(xdecimated[i], ydecimated[i]))
            points.append(QPointF(xdecimated[i], ydecimated[i]))

        curve.replace(points)
        points.clear()

        if legend_text is not None:
            curve.setName(legend_text)

        # Needed for mouse events on series
        self.chart.setAcceptHoverEvents(True)
        self.xvalues[self.ncurves] = np.array(xdecimated)

        # connect signals / slots
        # curve.clicked.connect(self.lineseries_clicked)
        # curve.hovered.connect(self.lineseries_hovered)

        # Add series
        self.chart.addSeries(curve)
        self.ncurves += 1
        self.update_axes()

    def update_data(self, xdata, ydata, series_id):
        if series_id < len(self.chart.series()):
            # Find start time to replace data
            current_series = self.chart.series()[series_id]
            current_points = current_series.pointsVector()

            # Find start and end indexes
            start_index = -1
            try:
                start_index = current_points.index(
                    QPointF(xdata[0] * 1000, ydata[0]))  # Right on the value!
                # print("update_data: start_index found exact match.")
            except ValueError:
                # print("update_data: start_index no exact match - scanning deeper...")
                for i, value in enumerate(current_points):
                    # print(str(current_points[i].x()) + " == " + str(xdata[0]*1000))
                    if current_points[i].x() == xdata[0] * 1000 or (
                            i > 0 and current_points[i - 1].x() <
                            xdata[0] * 1000 < current_points[i].x()):
                        start_index = i
                        # print("update_data: start_index found approximative match.")
                        break

            end_index = -1
            try:
                end_index = current_points.index(
                    QPointF(xdata[len(xdata) - 1] * 1000,
                            ydata[len(ydata) - 1]))  # Right on!
                # print("update_data: start_index found exact match.")
            except ValueError:
                # print("update_data: start_index no exact match - scanning deeper...")
                for i, value in enumerate(current_points):
                    # print(str(current_points[i].x()) + " == " + str(xdata[0]*1000))
                    if current_points[i].x(
                    ) == xdata[len(xdata) - 1] * 1000 or (
                            i > 0 and
                            current_points[i - 1].x() < xdata[len(xdata) - 1] *
                            1000 < current_points[i].x()):
                        end_index = i
                        # print("update_data: start_index found approximative match.")
                        break

            if start_index < 0 or end_index < 0:
                return

            # Decimate, if needed
            xdata, ydata = self.decimate(xdata, ydata)

            # Check if we have the same number of points for that range. If not, remove and replace!
            target_points = current_points[start_index:end_index]
            if len(target_points) != len(xdata):
                points = []
                for i, value in enumerate(xdata):
                    # TODO improve
                    points.append(QPointF(value * 1000, ydata[i]))

                new_points = current_points[0:start_index] + points[0:len(points)-1] + \
                             current_points[end_index:len(current_points)-1]

                current_series.replace(new_points)
                new_points.clear()

                # self.xvalues[series_id] = np.array(xdata)

        return

    @classmethod
    def set_title(cls, title):
        # print('Setting title: ', title)
        # self.chart.setTitle(title)
        return

    @staticmethod
    def series_to_polyline(xdata, ydata):
        """Convert series data to QPolygon(F) polyline

        This code is derived from PythonQwt's function named
        `qwt.plot_curve.series_to_polyline`"""

        # print('series_to_polyline types:', type(xdata[0]), type(ydata[0]))
        size = len(xdata)
        polyline = QPolygonF(size)

        # for i in range(0, len(xdata)):
        #   polyline[i] = QPointF(xdata[i] - xdata[0], ydata[i])
        for i, data in enumerate(xdata):
            polyline[i] = QPointF(data - xdata[0], ydata[i])

        # pointer = polyline.data()
        # dtype, tinfo = np.float, np.finfo  # integers: = np.int, np.iinfo
        # pointer.setsize(2*polyline.size()*tinfo(dtype).dtype.itemsize)
        # memory = np.frombuffer(pointer, dtype)
        # memory[:(size-1)*2+1:2] = xdata
        # memory[1:(size-1)*2+2:2] = ydata
        return polyline

    def add_test_data(self):

        # 100Hz, one day accelerometer values
        npoints = 1000 * 60 * 24

        xdata = np.linspace(0., 10., npoints)
        self.add_data(xdata, np.sin(xdata), color=Qt.red, legend_text='Acc. X')
        # self.add_data(xdata, np.cos(xdata), color=Qt.green, legend_text='Acc. Y')
        # self.add_data(xdata, np.cos(2 * xdata), color=Qt.blue, legend_text='Acc. Z')
        self.set_title(
            "Simple example with %d curves of %d points (OpenGL Accelerated Series)"
            % (self.ncurves, npoints))

    def mouseMoveEvent(self, e: QMouseEvent):
        if self.selecting:

            clicked_x = max(
                self.chart.plotArea().x(),
                min(
                    self.mapToScene(e.pos()).x(),
                    self.chart.plotArea().x() + self.chart.plotArea().width()))

            clicked_y = max(
                self.chart.plotArea().y(),
                min(
                    self.mapToScene(e.pos()).y(),
                    self.chart.plotArea().y() +
                    self.chart.plotArea().height()))

            current_pos = QPoint(clicked_x, clicked_y)  # e.pos()

            self.selection_stop_time = self.chart.mapToValue(
                QPointF(clicked_x, 0)).x()

            self.setCursorPosition(clicked_x, True)

            if self.interaction_mode == GraphInteractionMode.SELECT:
                if current_pos.x() < self.initialClick.x():
                    start_x = current_pos.x()
                    width = self.initialClick.x() - start_x
                else:
                    start_x = self.initialClick.x()
                    width = current_pos.x() - self.initialClick.x()

                self.selectionBand.setGeometry(
                    QRect(start_x,
                          self.chart.plotArea().y(), width,
                          self.chart.plotArea().height()))
                if self.selection_rec:
                    self.selection_rec.setRect(
                        QRectF(start_x,
                               self.chart.plotArea().y(), width,
                               self.chart.plotArea().height()))

            if self.interaction_mode == GraphInteractionMode.MOVE:
                new_pos = current_pos - self.initialClick
                self.chart.scroll(-new_pos.x(), new_pos.y())
                self.initialClick = current_pos

    def mousePressEvent(self, e: QMouseEvent):
        # Handling rubberbands
        # super().mousePressEvent(e)
        # Verify if click is inside plot area
        # if self.chart.plotArea().contains(e.pos()):

        self.selecting = True
        clicked_x = max(
            self.chart.plotArea().x(),
            min(
                self.mapToScene(e.pos()).x(),
                self.chart.plotArea().x() + self.chart.plotArea().width()))

        clicked_y = max(
            self.chart.plotArea().y(),
            min(
                self.mapToScene(e.pos()).y(),
                self.chart.plotArea().y() + self.chart.plotArea().height()))

        self.initialClick = QPoint(clicked_x, clicked_y)  # e.pos()

        self.selection_start_time = self.chart.mapToValue(QPointF(
            clicked_x, 0)).x()

        self.setCursorPosition(clicked_x, True)

        if self.interaction_mode == GraphInteractionMode.SELECT:
            self.selectionBand.setGeometry(
                QRect(self.initialClick.x(),
                      self.chart.plotArea().y(), 1,
                      self.chart.plotArea().height()))
            self.selectionBand.show()

        if self.interaction_mode == GraphInteractionMode.MOVE:
            QGuiApplication.setOverrideCursor(Qt.ClosedHandCursor)
            self.labelValue.setVisible(False)

    def mouseReleaseEvent(self, e: QMouseEvent):

        # Assure if click is inside plot area
        # Handling rubberbands (with min / max x)
        clicked_x = max(
            self.chart.plotArea().x(),
            min(
                self.mapToScene(e.pos()).x(),
                self.chart.plotArea().x() + self.chart.plotArea().width()))

        if self.interaction_mode == GraphInteractionMode.SELECT:
            self.selectionBand.hide()
            if clicked_x != self.mapToScene(self.initialClick).x():
                mapped_x = self.mapToScene(self.initialClick).x()
                if self.initialClick.x() < clicked_x:
                    self.setSelectionArea(mapped_x, clicked_x, True)
                else:
                    self.setSelectionArea(clicked_x, mapped_x, True)

        if self.interaction_mode == GraphInteractionMode.MOVE:
            QGuiApplication.restoreOverrideCursor()

        self.selecting = False

    def clearSelectionArea(self, emit_signal=False):
        if self.selection_rec:
            self.scene().removeItem(self.selection_rec)
            self.selection_rec = None

        if emit_signal:
            self.clearedSelectionArea.emit()

    def setSelectionArea(self, start_pos, end_pos, emit_signal=False):
        selection_brush = QBrush(QColor(153, 204, 255, 128))
        selection_pen = QPen(Qt.transparent)

        if self.selection_rec:
            self.scene().removeItem(self.selection_rec)

        self.selection_rec = self.scene().addRect(
            start_pos,
            self.chart.plotArea().y(), end_pos - start_pos,
            self.chart.plotArea().height(), selection_pen, selection_brush)
        if emit_signal:
            self.selectedAreaChanged.emit(
                self.chart.mapToValue(QPointF(start_pos, 0)).x(),
                self.chart.mapToValue(QPointF(end_pos, 0)).x())

    def setSelectionAreaFromTime(self,
                                 start_time,
                                 end_time,
                                 emit_signal=False):
        # Convert times to x values
        if isinstance(start_time, datetime.datetime):
            start_time = start_time.timestamp() * 1000
        if isinstance(end_time, datetime.datetime):
            end_time = end_time.timestamp() * 1000

        start_pos = self.chart.mapToPosition(QPointF(start_time, 0)).x()
        end_pos = self.chart.mapToPosition(QPointF(end_time, 0)).x()

        self.setSelectionArea(start_pos, end_pos)

    def setCursorPosition(self, pos, emit_signal=False):

        self.cursor_time = self.chart.mapToValue(QPointF(pos, 0)).x()

        # print (pos)
        pen = self.cursor.pen()
        pen.setColor(Qt.cyan)
        pen.setWidthF(1.0)
        self.cursor.setPen(pen)
        # On Top
        self.cursor.setZValue(100.0)

        area = self.chart.plotArea()
        x = pos
        y1 = area.y()
        y2 = area.y() + area.height()

        # self.cursor.set
        self.cursor.setLine(x, y1, x, y2)
        self.cursor.show()

        xmap_initial = self.chart.mapToValue(QPointF(pos, 0)).x()
        display = ''
        # '<i>' + (datetime.datetime.fromtimestamp(xmap + self.reftime.timestamp())).strftime('%d-%m-%Y %H:%M:%S') +
        # '</i><br />'
        ypos = 10
        last_val = None
        for i in range(self.ncurves):
            # Find nearest point
            idx = (np.abs(self.xvalues[i] - xmap_initial)).argmin()
            ymap = self.chart.series()[i].at(idx).y()
            xmap = self.chart.series()[i].at(idx).x()
            if i == 0:
                display += "<i>" + (datetime.datetime.fromtimestamp(xmap_initial/1000)).strftime('%d-%m-%Y %H:%M:%S:%f') + \
                           "</i>"

            # Compute where to display label
            if last_val is None or ymap > last_val:
                last_val = ymap
                ypos = self.chart.mapToPosition(QPointF(xmap, ymap)).y()
            if display != '':
                display += '<br />'

            display += self.chart.series()[i].name(
            ) + ': <b>' + '%.3f' % ymap + '</b>'

        self.labelValue.setText(display)
        self.labelValue.setGeometry(pos, ypos, 100, 100)
        self.labelValue.adjustSize()
        self.labelValue.setVisible(True)

        if emit_signal:
            self.cursorMoved.emit(xmap_initial)

        self.update()

    def setCursorPositionFromTime(self, timestamp, emit_signal=False):
        # Find nearest point
        if isinstance(timestamp, datetime.datetime):
            timestamp = timestamp.timestamp()
        pos = self.get_pos_from_time(timestamp)
        self.setCursorPosition(pos, emit_signal)

    def get_pos_from_time(self, timestamp):
        px = timestamp
        idx1 = (np.abs(self.xvalues[0] - px)).argmin()
        x1 = self.chart.series()[0].at(idx1).x()
        pos1 = self.chart.mapToPosition(QPointF(x1, 0)).x()
        idx2 = idx1 + 1
        if idx2 < len(self.chart.series()[0]):
            x2 = self.chart.series()[0].at(idx2).x()
            if x2 != x1:
                pos2 = self.chart.mapToPosition(QPointF(x2, 0)).x()
                x2 /= 1000
                x1 /= 1000
                pos = (((px - x1) / (x2 - x1)) * (pos2 - pos1)) + pos1
            else:
                pos = pos1
        else:
            pos = pos1
        return pos

    def resizeEvent(self, e: QResizeEvent):
        super().resizeEvent(e)

        # oldSize = e.oldSize()
        # newSize = e.size()

        # Update cursor from time
        if self.cursor_time:
            self.setCursorPositionFromTime(self.cursor_time / 1000.0)

        # Update selection
        if self.selection_rec:
            self.setSelectionAreaFromTime(self.selection_start_time,
                                          self.selection_stop_time)

    def zoom_in(self):
        self.chart.zoomIn()
        self.update_axes()

    def zoom_out(self):
        self.chart.zoomOut()
        self.update_axes()

    def zoom_area(self):
        if self.selection_rec:
            zoom_rec = self.selection_rec.rect()
            zoom_rec.setY(0)
            zoom_rec.setHeight(self.chart.plotArea().height())
            self.chart.zoomIn(zoom_rec)
            self.clearSelectionArea(True)
            self.update_axes()

    def zoom_reset(self):
        self.chart.zoomReset()
        self.update_axes()

    def get_displayed_start_time(self):
        min_x = self.chart.mapToScene(self.chart.plotArea()).boundingRect().x()
        xmap = self.chart.mapToValue(QPointF(min_x, 0)).x()
        return datetime.datetime.fromtimestamp(xmap / 1000)

    def get_displayed_end_time(self):
        max_x = self.chart.mapToScene(self.chart.plotArea()).boundingRect().x()
        max_x += self.chart.mapToScene(
            self.chart.plotArea()).boundingRect().width()
        xmap = self.chart.mapToValue(QPointF(max_x, 0)).x()
        return datetime.datetime.fromtimestamp(xmap / 1000)

    @property
    def is_zoomed(self):
        return self.chart.isZoomed()