class Spline(QGraphicsPathItem): """Class that describes a spline""" def __init__(self, points, color): self.setKnotPoints(points) if color == 'y': self.setPen(QPen(Qt.yellow, 2)) elif color == "r": self.setPen(QPen(Qt.red, 2)) else: self.setPen(QPen(Qt.blue, 2)) def setKnotPoints(self, knotPoints): """KnotPoints is a list of points""" p1 = QPointF(knotPoints[0][0], knotPoints[1][0]) self.path = QPainterPath(p1) super(Spline, self).__init__(self.path) self.points = self.interpolate(knotPoints) for i in range(0, len(self.points[0])): self.path.lineTo(self.points[0][i], self.points[1][i]) self.setPath(self.path) self.path.closeSubpath() self.knotPoints = knotPoints def interpolate(self, pts): """Interpolates the spline points at 500 points along spline""" pts = np.array(pts) tck, u = splprep(pts, u=None, s=0.0, per=1) u_new = np.linspace(u.min(), u.max(), 500) x_new, y_new = splev(u_new, tck, der=0) return (x_new, y_new) def update(self, pos, idx): """Updates the stored spline everytime it is moved Args: pos: new points coordinates idx: index on spline of updated point """ if idx == len(self.knotPoints[0]) + 1: self.knotPoints[0].append(pos.x()) self.knotPoints[1].append(pos.y()) else: self.knotPoints[0][idx] = pos.x() self.knotPoints[1][idx] = pos.y() self.points = self.interpolate(self.knotPoints) for i in range(0, len(self.points[0])): self.path.setElementPositionAt(i, self.points[0][i], self.points[1][i]) self.setPath(self.path)
class Spline(QGraphicsPathItem): def __init__(self, points, color): self.setKnotPoints(points) if color == 'y': self.setPen(QPen(Qt.yellow, 2)) else: self.setPen(QPen(Qt.red, 2)) def setKnotPoints(self, knotPoints): """KnotPoints is a list of points""" p1 = QPointF(knotPoints[0][0], knotPoints[1][0]) self.path = QPainterPath(p1) super(Spline, self).__init__(self.path) self.points = self.interpolate(knotPoints) for i in range(0, len(self.points[0])): self.path.lineTo(self.points[0][i], self.points[1][i]) self.setPath(self.path) self.path.closeSubpath() self.knotPoints = knotPoints def interpolate(self, pts): pts = np.array(pts) tck, u = splprep(pts, u=None, s=0.0, per=1) u_new = np.linspace(u.min(), u.max(), 500) x_new, y_new = splev(u_new, tck, der=0) return (x_new, y_new) def update(self, pos, idx): self.knotPoints[0][idx] = pos.x() self.knotPoints[1][idx] = pos.y() self.points = self.interpolate(self.knotPoints) for i in range(0, len(self.points[0])): self.path.setElementPositionAt(i, self.points[0][i], self.points[1][i]) self.setPath(self.path)
class Canvas(QWidget): def __init__(self, parent=None): super(Canvas, self).__init__(parent) self.is_enable_knee_control = False # 画像専用のレイヤであるかを制御する # 1度Trueになったら2度とFalseにならないことを意図する self.is_picture_canvas = False self.picture_file_name = "" self.image = QImage() # マウストラック有効化 self.setMouseTracking(True) # マウス移動で出る予測線とクリックして出る本線を描画するときに区別する self.is_line_prediction = False # イベント同士の競合を防ぐ self.event_Locker = False self.rounded_polygon = RoundedPolygon(10000) self.existing_paths = [] # 確定したパスを保存 self.recorded_points = [] # 確定した点を保存(実験の記録用) self.clicked_points = [] # 今描いている線の制御点を記録 self.cursor_position = QPointF() self.cursor_position_mousePressed = QPointF() self.knee_position = QPointF() self.knee_position_mousePressed = QPointF() self.current_drawing_mode = OperationMode.DRAWING_POINTS self.current_knee_operation_mode = OperationMode.NONE self.__line_color = [] self.current_line_color = QColor() self.nearest_path = QPainterPath() self.nearest_distance = 50.0 self.nearest_index = 0 self.is_dragging = False self.pen_width = 2 self.show() def set_experiment_controller(self, excontroller): self.experiment_controller = excontroller def mousePressEvent(self, event: QMouseEvent): if self.current_drawing_mode == OperationMode.DRAWING_POINTS: # 制御点の追加 if event.button() == Qt.LeftButton: self.clicked_points.append(event.pos()) # print(self.clickedPoints) # 直前の制御点の消去 if event.button() == Qt.RightButton: if len(self.clicked_points) > 0: self.clicked_points.pop() self.update() elif self.current_drawing_mode == OperationMode.MOVING_POINTS: if event.button() == Qt.LeftButton: self.is_dragging = True if self.is_enable_knee_control: self.recode_knee_and_cursor_position() self.cursor_position = event.pos() self.update() def mouseMoveEvent(self, event: QMouseEvent): self.experiment_controller.current_mouse_position = event.pos() self.experiment_controller.record_frame( self.current_drawing_mode, self.current_knee_operation_mode) if self.current_drawing_mode == OperationMode.DRAWING_POINTS: self.clicked_points.append(event.pos()) self.is_line_prediction = True self.update() elif self.current_drawing_mode == OperationMode.MOVING_POINTS: print(self.nearest_distance) self.cursor_position = event.pos() if self.is_dragging: self.move_point() self.update() def mouseReleaseEvent(self, event: QMouseEvent): self.is_dragging = False def paintEvent(self, event: QPaintEvent): # if not self.event_Locker: painter = QPainter(self) if self.is_picture_canvas: painter.drawImage(QRect(0, 0, 600, 600), self.image) else: # すでに確定されているパスの描画 if len(self.existing_paths) > 0: for i in range(len(self.existing_paths)): print("linecolor {}: {}".format( i, self.__line_color[i].hue())) painter.setPen(QPen(self.__line_color[i], self.pen_width)) painter.drawPath(self.existing_paths[i]) if self.current_drawing_mode == OperationMode.DRAWING_POINTS: # 現在描いているパスの描画 if len(self.clicked_points) > 3: painter.setPen( QPen(self.current_line_color, self.pen_width)) # print(self.clickedPoints) # クリックした点まで線を伸ばすため、終点を一時的にリストに入れている self.clicked_points.append( self.clicked_points[len(self.clicked_points) - 1]) painter_path = self.rounded_polygon.get_path( self.clicked_points) # 設置した点の描画 painter.setPen(Qt.black) for i in range(len(self.clicked_points)): painter.drawEllipse(self.clicked_points[i], 2, 2) painter.setPen( QPen(self.current_line_color, self.pen_width)) # 現在のマウス位置での予告線 if self.is_line_prediction: self.clicked_points.pop() self.is_line_prediction = False painter.drawPath(painter_path) self.clicked_points.pop() # 線が描けない時 else: # 現在のマウス位置での予告線 if self.is_line_prediction: painter.setPen(Qt.red) for i in range(len(self.clicked_points)): painter.drawEllipse(self.clicked_points[i], 2, 2) if not len(self.clicked_points) == 0: self.clicked_points.pop() self.is_line_prediction = False # 予告線でもない場合は単に点を書く else: for i in range(len(self.clicked_points)): painter.drawEllipse(self.clicked_points[i], 2, 2) # 制御点を移動するとき elif self.current_drawing_mode == OperationMode.MOVING_POINTS: # すでに確定されているパスの制御点の描画 self.nearest_distance = 50.0 painter.setPen(Qt.black) for path in self.existing_paths: for i in range(path.elementCount()): control_point = QPointF( path.elementAt(i).x, path.elementAt(i).y) painter.drawEllipse(control_point, 3, 3) # 現在のカーソル位置から最も近い点と、その点が属するpathを記録、更新 # if not self.is_dragging & self.is_enable_knee_control: distance = math.sqrt( (control_point.x() - self.cursor_position.x())**2 + (control_point.y() - self.cursor_position.y())**2) if distance < self.nearest_distance: self.nearest_distance = distance self.nearest_path = path self.nearest_index = i # 一定の距離未満かつ最も近い点を赤く描画 if self.nearest_distance < 20: painter.setPen(QPen(Qt.red, self.pen_width)) nearest_control_point = QPointF( self.nearest_path.elementAt(self.nearest_index).x, self.nearest_path.elementAt(self.nearest_index).y) painter.drawEllipse(nearest_control_point, 3, 3) def move_point(self): if self.is_enable_knee_control: if self.nearest_distance < 20 or self.is_dragging: self.nearest_path.setElementPositionAt( self.nearest_index, self.cursor_position.x(), self.cursor_position.y()) amount_of_change = QPointF( self.cursor_position.x() + (self.knee_position.x() - self.knee_position_mousePressed.x()), self.cursor_position.y() - (self.knee_position.y() - self.knee_position_mousePressed.y())) self.nearest_path.setElementPositionAt(self.nearest_index, amount_of_change.x(), amount_of_change.y()) else: if self.nearest_distance < 20: self.nearest_path.setElementPositionAt( self.nearest_index, self.cursor_position.x(), self.cursor_position.y()) def set_knee_position(self, x, y): self.knee_position.setX(x) self.knee_position.setY(y) if self.is_dragging: if self.current_drawing_mode == OperationMode.MOVING_POINTS: self.move_point() self.update() def set_line_color(self, color): self.current_line_color = color def recode_knee_and_cursor_position(self): self.knee_position_mousePressed.setX(self.knee_position.x()) self.knee_position_mousePressed.setY(self.knee_position.y()) self.cursor_position_mousePressed = self.cursor_position def fix_path(self): # パスを確定 if self.is_line_prediction and not len(self.clicked_points) == 0: self.clicked_points.pop() # クリックした点まで線を伸ばすため、終点をリストに入れている if len(self.clicked_points) > 0: self.clicked_points.append( self.clicked_points[len(self.clicked_points) - 1]) painter_path = self.rounded_polygon.get_path(self.clicked_points) # 線と色を記録 self.existing_paths.append(painter_path) self.__line_color.append(self.current_line_color) for i in range(len(self.__line_color)): print("{}, {}".format(i, self.__line_color[i].value())) self.clicked_points.pop() self.recorded_points.append(self.clicked_points) # 点をリセット self.clicked_points = [] self.update() def delete_last_path(self): if len(self.existing_paths) > 0: self.existing_paths.pop() self.__line_color.pop() self.update() def switch_visible(self, is_visible: bool): palette = self.palette() if is_visible: palette.setColor(QPalette.Background, QColor(255, 255, 255, 120)) else: palette.setColor(QPalette.Background, QColor(255, 255, 255, 255)) self.setPalette(palette) def operation_mode_changed(self, to_drawing: OperationMode, to_knee: OperationMode): self.current_drawing_mode = to_drawing self.current_knee_operation_mode = to_knee self.fix_path() def set_picture_file_name(self, picture_file_name: str): self.is_picture_canvas = True self.picture_file_name = picture_file_name self.update() def set_enable_knee_control(self, is_enable_knee_control): self.is_enable_knee_control = is_enable_knee_control def load_picture(self, image: QImage): self.image = image self.is_picture_canvas = True self.update()