def __init__(self, name="?", r=10): super().__init__() self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsFocusable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setAcceptHoverEvents(True) # define circle shape: w = 2 * r + 2 self.el = QGraphicsEllipseItem(0, 0, w, w) self.el.setBrush(QBrush(QColor("white"))) shadow = QGraphicsDropShadowEffect() shadow.setOffset(4) self.el.setGraphicsEffect(shadow) self.el.setParentItem(self) # define node label shape: self.label = QGraphicsTextItem(name) self.label.setDefaultTextColor(QColor("blue")) self.label.setFlag(QGraphicsItem.ItemIsSelectable) self.label.setParentItem(self) self.el.setZValue(1.0) self.label.setZValue(2.0) center = self.center() - self.label.boundingRect().center() self.label.setPos(self.mapFromScene(center)) self.setZValue(1.0) self.cx = []
def draw_cursor(self, event_x, event_y, drawing_tool_radius, new_circle=False): """ Draws a blue circle where the user clicked. :param event_x: QGraphicsScene event attribute: event.scenePos().x() :param event_y: QGraphicsScene event attribute: event.scenePos().y() :param drawing_tool_radius: the current radius of the drawing tool :param new_circle: True when the circle object is being created rather than updated. """ self.draw_tool_radius = drawing_tool_radius self.current_cursor_x = event_x - self.draw_tool_radius self.current_cursor_y = event_y - self.draw_tool_radius if new_circle: self.cursor = QGraphicsEllipseItem(self.current_cursor_x, self.current_cursor_y, self.draw_tool_radius * 2, self.draw_tool_radius * 2) pen = QPen(QColor("blue")) pen.setWidth(0) self.cursor.setPen(pen) self.cursor.setZValue(1) self.addItem(self.cursor) elif self.cursor is not None: self.cursor.setRect(self.current_cursor_x, self.current_cursor_y, self.draw_tool_radius * 2, self.draw_tool_radius * 2)
def __init__(self, shape, item_type, pixmap): self.shape: QGraphicsItem = shape self.name: str = "undefined" if item_type == QGraphicsRectItem.type(QGraphicsRectItem()): self.name: str = "rectangle" elif item_type == QGraphicsEllipseItem.type(QGraphicsEllipseItem()): self.name: str = "ellipse" elif item_type == QGraphicsPolygonItem.type(QGraphicsPolygonItem()): if shape.polygon().size() == 3: self.name: str = "triangle" else: self.name: str = "polygon" elif item_type == QGraphicsLineItem.type(QGraphicsLineItem()): self.name: str = "line" self.pixmap: QPixmap = pixmap
def shape_to_pixelmap(item_type, pen, brush, shape) -> QPixmap: pixmap = QPixmap(50, 50) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(pen) painter.setBrush(brush) if item_type == QGraphicsRectItem.type(QGraphicsRectItem()): painter.drawRect(QRect(10, 15, 30, 20)) elif item_type == QGraphicsEllipseItem.type(QGraphicsEllipseItem()): painter.drawEllipse(QRect(10, 10, 30, 30)) elif item_type == QGraphicsPolygonItem.type(QGraphicsPolygonItem()): if shape.polygon().size() == 3: painter.drawPolygon(QPolygon([QPoint(10, 40), QPoint(40, 40), QPoint(25, 10)])) else: painter.drawPolygon(QPolygon([QPoint(12, 40), QPoint(23, 36), QPoint(37, 24), QPoint(23, 12), QPoint(7, 16)])) elif item_type == QGraphicsLineItem.type(QGraphicsLineItem()): painter.drawLine(QLine(10, 40, 40, 10)) return pixmap
def draw_simple_shape_to_scene(view: QGraphicsView, p_prev: QPointF, p_act: QPointF, to_gim: bool = True) -> None: if view.actual_selection.value in [2, 4]: width = math.fabs(p_prev.x() - p_act.x()) height = math.fabs(p_prev.y() - p_act.y()) xmin = min(p_prev.x(), p_act.x()) ymin = min(p_prev.y(), p_act.y()) item: QAbstractGraphicsShapeItem if view.actual_selection.value == 2: item = QGraphicsRectItem(0, 0, width, height) else: item = QGraphicsEllipseItem(0, 0, width, height) item.setPos(QPointF(xmin, ymin)) item.setPen(view.actual_pen) item.setBrush(view.actual_brush) DrawTools.set_item_flags(item) view.scene().addItem(item) view.gim.append_shape(item, item.type(), view.actual_pen, view.actual_brush) elif view.actual_selection.value in [3, 5, 6]: x = p_act.x() - p_prev.x() y = p_act.y() - p_prev.y() item: QGraphicsItem if view.actual_selection.value == 3: item = QGraphicsPolygonItem( QPolygonF( [QPointF(0, 0), QPointF(0 + x, 0), QPointF(x / 2, y)])) item.setBrush(view.actual_brush) else: item = QGraphicsLineItem(QLineF(0, 0, x, y)) item.setPos(p_prev) item.setPen(view.actual_pen) DrawTools.set_item_flags(item) view.scene().addItem(item) if to_gim: view.gim.append_shape(item, item.type(), view.actual_pen, view.actual_brush)
def load_items(self, file_name: str, gim): input_file = QFile(file_name) if input_file.open(QIODevice.ReadOnly): input_stream = QDataStream(input_file) while not input_stream.atEnd(): item_type = input_stream.readInt8() item_name = input_stream.readString() if item_type == QGraphicsRectItem.type(QGraphicsRectItem()): rect = QGraphicsRectItem() self.input_to_rect_item(input_stream, rect) self.scene.addItem(rect) gim.append_shape(rect, rect.type(), rect.pen(), rect.brush()) print("Rectangle loaded") elif item_type == QGraphicsEllipseItem.type( QGraphicsEllipseItem()): ellipse = QGraphicsEllipseItem() self.input_to_rect_item(input_stream, ellipse) self.scene.addItem(ellipse) gim.append_shape(ellipse, ellipse.type(), ellipse.pen(), ellipse.brush()) print("Ellipse loaded") elif item_type == QGraphicsPolygonItem.type( QGraphicsPolygonItem()): polygon = QGraphicsPolygonItem() self.input_to_polygon_item(input_stream, polygon) self.scene.addItem(polygon) gim.append_shape(polygon, polygon.type(), polygon.pen(), polygon.brush()) print("l polygon") elif item_type == QGraphicsLineItem.type(QGraphicsLineItem()): line = QGraphicsLineItem() self.input_to_line_item(input_stream, line) self.scene.addItem(line) gim.append_shape(line, line.type(), line.pen(), QBrush(Qt.black)) print("Line loaded") gim.return_shapes()[-1].name = item_name ItemsInputOutput.set_item_flags(self.scene) input_file.close()
class Drawing(QtWidgets.QGraphicsScene): """ Class responsible for the ROI drawing functionality """ # Initialisation function of the class def __init__(self, imagetoPaint, pixmapdata, min_pixel, max_pixel, dataset, draw_roi_window_instance, slice_changed, current_slice, drawing_tool_radius, keep_empty_pixel, target_pixel_coords=set()): super(Drawing, self).__init__() # create the canvas to draw the line on and all its necessary # components self.dataset = dataset self.rows = dataset['Rows'].value self.cols = dataset['Columns'].value self.draw_roi_window_instance = draw_roi_window_instance self.slice_changed = slice_changed self.current_slice = current_slice self.min_pixel = min_pixel self.max_pixel = max_pixel self.addItem(QGraphicsPixmapItem(imagetoPaint)) self.img = imagetoPaint self.data = pixmapdata self.values = [] self.getValues() self.rect = QtCore.QRect(250, 300, 20, 20) self.update() self._points = {} self._circlePoints = [] self.drag_position = QtCore.QPoint() self.cursor = None self.polygon_preview = [] self.isPressed = False self.pixel_array = None self.pen = QtGui.QPen(QtGui.QColor("yellow")) self.pen.setStyle(QtCore.Qt.DashDotDotLine) # This will contain the new pixel coordinates specified by the min # and max pixel density self.target_pixel_coords = target_pixel_coords self.according_color_dict = {} self.q_image = None self.q_pixmaps = None self.label = QtWidgets.QLabel() self.draw_tool_radius = drawing_tool_radius self.is_current_pixel_coloured = False self.keep_empty_pixel = keep_empty_pixel self._display_pixel_color() def _display_pixel_color(self): """ Creates the initial list of pixel values within the given minimum and maximum densities, then displays them on the view. """ if self.min_pixel <= self.max_pixel: data_set = self.dataset if hasattr(self.draw_roi_window_instance, 'bounds_box_draw'): bound_box = \ self.draw_roi_window_instance.bounds_box_draw.box.rect() self.min_x, self.min_y = linear_transform( bound_box.x(), bound_box.y(), self.rows, self.cols ) self.max_x, self.max_y = linear_transform( bound_box.width(), bound_box.height(), self.rows, self.cols ) self.max_x += self.min_x self.max_y += self.min_y else: self.min_x = 0 self.min_y = 0 self.max_x = self.rows self.max_y = self.cols """pixel_array is a 2-Dimensional array containing all pixel coordinates of the q_image. pixel_array[x][y] will return the density of the pixel """ self.pixel_array = data_set._pixel_array self.q_image = self.img.toImage() for y_coord in range(self.min_y, self.max_y): for x_coord in range(self.min_x, self.max_x): if (self.pixel_array[y_coord][x_coord] >= self.min_pixel) \ and (self.pixel_array[y_coord][ x_coord] <= self.max_pixel): self.target_pixel_coords.add((x_coord, y_coord)) """For the meantime, a new image is created and the pixels specified are coloured. This will need to altered so that it creates a new layer over the existing image instead of replacing it. """ # Convert QPixMap into Qimage for x_coord, y_coord in self.target_pixel_coords: temp = set() temp.add((x_coord, y_coord)) points = get_pixel_coords(temp, self.rows, self.cols) temp_2 = get_first_entry(points) c = self.q_image.pixel(temp_2[0], temp_2[1]) colors = QColor(c).getRgbF() self.according_color_dict[(x_coord, y_coord)] = colors color = QtGui.QColor() color.setRgb(90, 250, 175, 200) points = get_pixel_coords(self.according_color_dict, self.rows, self.cols) for x_coord, y_coord in points: self.q_image.setPixelColor(x_coord, y_coord, color) self.refresh_image() def _find_neighbor_point(self, event): """ Find point around mouse position. This function is for if we want to choose and drag the circle :rtype: ((int, int)|None) :param event: the mouse event :return: (x, y) if there are any point around mouse else None """ distance_threshold = 3.0 nearest_point = None min_distance = math.sqrt(2 * (100 ** 2)) for x, y in self._points.items(): distance = math.hypot(event.xdata - x, event.ydata - y) if distance < min_distance: min_distance = distance nearest_point = (x, y) if min_distance < distance_threshold: return nearest_point return None def getValues(self): """ This function gets the corresponding values of all the points in the drawn line from the dataset. """ for i in range(DEFAULT_WINDOW_SIZE): for j in range(DEFAULT_WINDOW_SIZE): x, y = linear_transform( i, j, self.rows, self.cols) self.values.append(self.data[x][y]) def refresh_image(self): """ Convert QImage containing modified CT slice with highlighted pixels into a QPixmap, and then display it onto the view. """ self.q_pixmaps = QtWidgets.QGraphicsPixmapItem( QtGui.QPixmap.fromImage(self.q_image)) self.addItem(self.q_pixmaps) def remove_pixels_within_circle(self, clicked_x, clicked_y): """ Removes all highlighted pixels within the selected circle and updates the image. :param clicked_x: the current x coordinate :param clicked_y: the current y coordinate """ # Calculate euclidean distance between each highlighted point and # the clicked point. If the distance is less than the radius, # remove it from the highlighted pixels. # The roi drawn on current slice is changed after several pixels are # modified self.slice_changed = True clicked_x, clicked_y = linear_transform( clicked_x, clicked_y, self.rows, self.cols) according_color_dict_key_list = list(self.according_color_dict.keys()) color_to_draw = QtGui.QColor() color_to_draw.setRgb(90, 250, 175, 200) for x, y in according_color_dict_key_list: colors = self.according_color_dict[(x, y)] clicked_point = numpy.array((clicked_x, clicked_y)) point_to_check = numpy.array((x, y)) distance = numpy.linalg.norm(clicked_point - point_to_check) if distance <= self.draw_tool_radius * ( float(self.rows) / DEFAULT_WINDOW_SIZE): temp = set() temp.add((x, y)) points = get_pixel_coords(temp, self.rows, self.cols) for x_t, y_t in points: self.q_image.setPixelColor(x_t, y_t, QColor.fromRgbF( colors[0], colors[1], colors[2], colors[3])) self.target_pixel_coords.remove((x, y)) self.according_color_dict.pop((x, y)) # The roi drawn on current slice is changed after several # pixels are modified self.slice_changed = True self.refresh_image() def fill_pixels_within_circle(self, clicked_x, clicked_y): """ Add all highlighted pixels within the selected circle and updates the image. :param clicked_x: the current x coordinate :param clicked_y: the current y coordinate """ # Calculate euclidean distance between each highlighted point and # the clicked point. If the distance is less than the radius, # add it to the highlighted pixels. # Set of points to color points_to_color = set() # The roi drawn on current slice is changed after several pixels are # modified self.slice_changed = True clicked_x, clicked_y = linear_transform( clicked_x, clicked_y, self.rows, self.cols) scaled_tool_radius = int(self.draw_tool_radius * ( float(self.rows) / DEFAULT_WINDOW_SIZE)) min_y_bound_square = math.floor(clicked_y) - scaled_tool_radius min_x_bound_square = math.floor(clicked_x) - scaled_tool_radius max_y_bound_square = math.floor(clicked_y) + scaled_tool_radius max_x_bound_square = math.floor(clicked_x) + scaled_tool_radius for y_coord in range( max(self.min_y, min_y_bound_square), min(self.max_y, max_y_bound_square)): for x_coord in range( max(self.min_x, min_x_bound_square), min(self.max_x, max_x_bound_square)): clicked_point = numpy.array((clicked_x, clicked_y)) point_to_check = numpy.array((x_coord, y_coord)) distance = numpy.linalg.norm( clicked_point - point_to_check) if (self.keep_empty_pixel or self.min_pixel <= self.pixel_array[y_coord][ x_coord] <= self.max_pixel) \ and distance <= scaled_tool_radius: temp = set() temp.add((x_coord, y_coord)) points = get_pixel_coords(temp, self.rows, self.cols) temp_2 = get_first_entry(points) c = self.q_image.pixel(temp_2[0], temp_2[1]) colors = QColor(c) if (x_coord, y_coord) not in self.according_color_dict: self.according_color_dict[ (x_coord, y_coord)] = colors.getRgbF() points_to_color.add((x_coord, y_coord)) self.target_pixel_coords.add((x_coord, y_coord)) # Color to draw color_to_draw = QtGui.QColor() color_to_draw.setRgb(90, 250, 175, 200) points = get_pixel_coords(points_to_color, self.rows, self.cols) for x_coord, y_coord in points: self.q_image.setPixelColor(x_coord, y_coord, color_to_draw) self.refresh_image() def clear_cursor(self, drawing_tool_radius): """ Clean the current cursor :param drawing_tool_radius: the current radius of the drawing tool """ self.draw_tool_radius = drawing_tool_radius if self.cursor: self.removeItem(self.cursor) self.cursor = False def draw_cursor(self, event_x, event_y, drawing_tool_radius, new_circle=False): """ Draws a blue circle where the user clicked. :param event_x: QGraphicsScene event attribute: event.scenePos().x() :param event_y: QGraphicsScene event attribute: event.scenePos().y() :param drawing_tool_radius: the current radius of the drawing tool :param new_circle: True when the circle object is being created rather than updated. """ self.draw_tool_radius = drawing_tool_radius self.current_cursor_x = event_x - self.draw_tool_radius self.current_cursor_y = event_y - self.draw_tool_radius if new_circle: self.cursor = QGraphicsEllipseItem(self.current_cursor_x, self.current_cursor_y, self.draw_tool_radius * 2, self.draw_tool_radius * 2) pen = QPen(QColor("blue")) pen.setWidth(0) self.cursor.setPen(pen) self.cursor.setZValue(1) self.addItem(self.cursor) elif self.cursor is not None: self.cursor.setRect(self.current_cursor_x, self.current_cursor_y, self.draw_tool_radius * 2, self.draw_tool_radius * 2) def draw_contour_preview(self, point_lists): """ Draws a polygon onto the view so the user can preview what their contour will look like once exported. :param list of point_lists: A list of lists of points ordered to form polygons. """ # Extract a list of lists of points list_of_qpoint_list = [] # Scale points for non-standard image if self.rows != DEFAULT_WINDOW_SIZE: for list_of_points in point_lists: qpoint_list = [] for point in list_of_points: x_arr, y_arr = inv_linear_transform( point[0], point[1], self.rows, self.cols) for x in x_arr: for y in y_arr: qpoint = QtCore.QPoint(x, y) qpoint_list.append(qpoint) list_of_qpoint_list.append(qpoint_list) else: for list_of_points in point_lists: qpoint_list = [] for point in list_of_points: qpoint = QtCore.QPoint(point[0], point[1]) qpoint_list.append(qpoint) list_of_qpoint_list.append(qpoint_list) # Remove previously added polygons for polygon in self.polygon_preview: self.removeItem(polygon) self.polygon_preview.clear() # Draw new polygons for qpoint_list in list_of_qpoint_list: polygon = QtGui.QPolygonF(qpoint_list) graphics_polygon_item = QtWidgets.QGraphicsPolygonItem(polygon) graphics_polygon_item.setPen(self.pen) self.polygon_preview.append(graphics_polygon_item) self.addItem(graphics_polygon_item) def mousePressEvent(self, event): """ This method is called to handle a mouse press event :param event: the mouse event """ if self.cursor: self.removeItem(self.cursor) self.isPressed = True if ( 2 * QtGui.QVector2D(event.pos() - self.rect.center()).length() < self.rect.width() ): self.drag_position = event.pos() - self.rect.topLeft() super().mousePressEvent(event) x, y = linear_transform( math.floor(event.scenePos().x()), math.floor(event.scenePos().y()), self.rows, self.cols) is_coloured = (x, y) in self.according_color_dict self.is_current_pixel_coloured = is_coloured self.draw_cursor(event.scenePos().x(), event.scenePos().y(), self.draw_tool_radius, new_circle=True) if self.is_current_pixel_coloured: self.fill_pixels_within_circle(event.scenePos().x(), event.scenePos().y()) else: self.remove_pixels_within_circle(event.scenePos().x(), event.scenePos().y()) self.update() def mouseMoveEvent(self, event): """ This method is called to handle a mouse move event :param event: the mouse event """ if not self.drag_position.isNull(): self.rect.moveTopLeft(event.pos() - self.drag_position) super().mouseMoveEvent(event) if self.cursor and self.isPressed: self.draw_cursor(event.scenePos().x(), event.scenePos().y(), self.draw_tool_radius) if self.is_current_pixel_coloured: self.fill_pixels_within_circle(event.scenePos().x(), event.scenePos().y()) else: self.remove_pixels_within_circle(event.scenePos().x(), event.scenePos().y()) self.update() def mouseReleaseEvent(self, event): """ This method is called to handle a mouse release event :param event: the mouse event """ self.isPressed = False self.drag_position = QtCore.QPoint() super().mouseReleaseEvent(event) self.update()
class Node_basic(QGraphicsItem): """Node_basic is a QGraphicsItem that represents a function node, used as a view for a cfg.node of code.func or code.xfunc object. The object is movable, focusable and accepts mouse-over events. It is composed of a shadowed circle of radius *r* colored in white, and a blue label set as the function's name. Arguments: name (string): string used as label for the Node_basic.label. r (int): radius of the Node_basic.el circle. Attributes: el (QGraphicsEllipseItem): the cicle object label (QGraphicsTextItem): the label object cx (list[Edge_basic]): list of edges views associated with the node """ def __init__(self, name="?", r=10): super().__init__() self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsFocusable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setAcceptHoverEvents(True) # define circle shape: w = 2 * r + 2 self.el = QGraphicsEllipseItem(0, 0, w, w) self.el.setBrush(QBrush(QColor("white"))) shadow = QGraphicsDropShadowEffect() shadow.setOffset(4) self.el.setGraphicsEffect(shadow) self.el.setParentItem(self) # define node label shape: self.label = QGraphicsTextItem(name) self.label.setDefaultTextColor(QColor("blue")) self.label.setFlag(QGraphicsItem.ItemIsSelectable) self.label.setParentItem(self) self.el.setZValue(1.0) self.label.setZValue(2.0) center = self.center() - self.label.boundingRect().center() self.label.setPos(self.mapFromScene(center)) self.setZValue(1.0) self.cx = [] def boundingRect(self): e = self.el.boundingRect() l = self.label.boundingRect() l = self.mapRectToItem(self, l) return e.united(l) def shape(self): e = self.el.shape() l = self.label.shape() l = self.mapToItem(self, l) return e.united(l) def paint(self, painter, option, widget=None): pass def center(self): return self.el.sceneBoundingRect().center() def focusOutEvent(self, event): self.label.setTextInteractionFlags(Qt.NoTextInteraction) super(Node_basic, self).focusOutEvent(event) def mouseDoubleClickEvent(self, event): if self.label.textInteractionFlags() == Qt.NoTextInteraction: self.label.setTextInteractionFlags(Qt.TextEditorInteraction) super(Node_basic, self).mouseDoubleClickEvent(event) def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionHasChanged: for e in self.cx: e.update_points() return super(Node_basic, self).itemChange(change, value) def contextMenuEvent(self, event): menu = QMenu() testAction = QAction("Test", None) testAction.triggered.connect(self.print_out) menu.addAction(testAction) menu.exec_(event.screenPos()) def print_out(self): print("Triggered")