|
@@ -1,8 +1,10 @@
|
|
|
import copy
|
|
|
import math
|
|
|
|
|
|
+import numpy as np
|
|
|
from qtpy import QtCore
|
|
|
from qtpy import QtGui
|
|
|
+import skimage.measure
|
|
|
|
|
|
from labelme.logger import logger
|
|
|
import labelme.utils
|
|
@@ -45,6 +47,7 @@ class Shape(object):
|
|
|
flags=None,
|
|
|
group_id=None,
|
|
|
description=None,
|
|
|
+ mask=None,
|
|
|
):
|
|
|
self.label = label
|
|
|
self.group_id = group_id
|
|
@@ -60,6 +63,7 @@ class Shape(object):
|
|
|
self.flags = flags
|
|
|
self.description = description
|
|
|
self.other_data = {}
|
|
|
+ self.mask = mask
|
|
|
|
|
|
self._highlightIndex = None
|
|
|
self._highlightMode = self.NEAR_VERTEX
|
|
@@ -76,16 +80,17 @@ class Shape(object):
|
|
|
# is used for drawing the pending line a different color.
|
|
|
self.line_color = line_color
|
|
|
|
|
|
- def setShapeRefined(self, points, point_labels, shape_type):
|
|
|
- self._shape_raw = (self.points, self.point_labels, self.shape_type)
|
|
|
+ def setShapeRefined(self, shape_type, points, point_labels, mask=None):
|
|
|
+ self._shape_raw = (self.shape_type, self.points, self.point_labels)
|
|
|
+ self.shape_type = shape_type
|
|
|
self.points = points
|
|
|
self.point_labels = point_labels
|
|
|
- self.shape_type = shape_type
|
|
|
+ self.mask = mask
|
|
|
|
|
|
def restoreShapeRaw(self):
|
|
|
if self._shape_raw is None:
|
|
|
return
|
|
|
- self.points, self.point_labels, self.shape_type = self._shape_raw
|
|
|
+ self.shape_type, self.points, self.point_labels = self._shape_raw
|
|
|
self._shape_raw = None
|
|
|
|
|
|
@property
|
|
@@ -104,6 +109,7 @@ class Shape(object):
|
|
|
"circle",
|
|
|
"linestrip",
|
|
|
"points",
|
|
|
+ "mask",
|
|
|
]:
|
|
|
raise ValueError("Unexpected shape_type: {}".format(value))
|
|
|
self._shape_type = value
|
|
@@ -171,26 +177,56 @@ class Shape(object):
|
|
|
return QtCore.QRectF(x1, y1, x2 - x1, y2 - y1)
|
|
|
|
|
|
def paint(self, painter):
|
|
|
- if self.points:
|
|
|
- color = (
|
|
|
- self.select_line_color if self.selected else self.line_color
|
|
|
+ if self.mask is None and not self.points:
|
|
|
+ return
|
|
|
+
|
|
|
+ color = self.select_line_color if self.selected else self.line_color
|
|
|
+ pen = QtGui.QPen(color)
|
|
|
+ # Try using integer sizes for smoother drawing(?)
|
|
|
+ pen.setWidth(max(1, int(round(2.0 / self.scale))))
|
|
|
+ painter.setPen(pen)
|
|
|
+
|
|
|
+ if self.mask is not None:
|
|
|
+ image_to_draw = np.zeros(self.mask.shape + (4,), dtype=np.uint8)
|
|
|
+ fill_color = (
|
|
|
+ self.select_fill_color.getRgb()
|
|
|
+ if self.selected
|
|
|
+ else self.fill_color.getRgb()
|
|
|
)
|
|
|
- pen = QtGui.QPen(color)
|
|
|
- # Try using integer sizes for smoother drawing(?)
|
|
|
- pen.setWidth(max(1, int(round(2.0 / self.scale))))
|
|
|
- painter.setPen(pen)
|
|
|
+ image_to_draw[self.mask] = fill_color
|
|
|
+ qimage = QtGui.QImage.fromData(
|
|
|
+ labelme.utils.img_arr_to_data(image_to_draw)
|
|
|
+ )
|
|
|
+ painter.drawImage(
|
|
|
+ int(round(self.points[0].x())),
|
|
|
+ int(round(self.points[0].y())),
|
|
|
+ qimage,
|
|
|
+ )
|
|
|
+
|
|
|
+ line_path = QtGui.QPainterPath()
|
|
|
+ contours = skimage.measure.find_contours(
|
|
|
+ np.pad(self.mask, pad_width=1)
|
|
|
+ )
|
|
|
+ for contour in contours:
|
|
|
+ contour += [self.points[0].y(), self.points[0].x()]
|
|
|
+ line_path.moveTo(contour[0, 1], contour[0, 0])
|
|
|
+ for point in contour[1:]:
|
|
|
+ line_path.lineTo(point[1], point[0])
|
|
|
+ painter.drawPath(line_path)
|
|
|
|
|
|
+ if self.points:
|
|
|
line_path = QtGui.QPainterPath()
|
|
|
vrtx_path = QtGui.QPainterPath()
|
|
|
negative_vrtx_path = QtGui.QPainterPath()
|
|
|
|
|
|
- if self.shape_type == "rectangle":
|
|
|
+ if self.shape_type in ["rectangle", "mask"]:
|
|
|
assert len(self.points) in [1, 2]
|
|
|
if len(self.points) == 2:
|
|
|
rectangle = self.getRectFromLine(*self.points)
|
|
|
line_path.addRect(rectangle)
|
|
|
- for i in range(len(self.points)):
|
|
|
- self.drawVertex(vrtx_path, i)
|
|
|
+ if self.shape_type == "rectangle":
|
|
|
+ for i in range(len(self.points)):
|
|
|
+ self.drawVertex(vrtx_path, i)
|
|
|
elif self.shape_type == "circle":
|
|
|
assert len(self.points) in [1, 2]
|
|
|
if len(self.points) == 2:
|
|
@@ -226,9 +262,10 @@ class Shape(object):
|
|
|
line_path.lineTo(self.points[0])
|
|
|
|
|
|
painter.drawPath(line_path)
|
|
|
- painter.drawPath(vrtx_path)
|
|
|
- painter.fillPath(vrtx_path, self._vertex_fill_color)
|
|
|
- if self.fill:
|
|
|
+ if vrtx_path.length() > 0:
|
|
|
+ painter.drawPath(vrtx_path)
|
|
|
+ painter.fillPath(vrtx_path, self._vertex_fill_color)
|
|
|
+ if self.fill and self.mask is None:
|
|
|
color = (
|
|
|
self.select_fill_color
|
|
|
if self.selected
|
|
@@ -281,6 +318,18 @@ class Shape(object):
|
|
|
return post_i
|
|
|
|
|
|
def containsPoint(self, point):
|
|
|
+ if self.mask is not None:
|
|
|
+ y = np.clip(
|
|
|
+ int(round(point.y() - self.points[0].y())),
|
|
|
+ 0,
|
|
|
+ self.mask.shape[0] - 1,
|
|
|
+ )
|
|
|
+ x = np.clip(
|
|
|
+ int(round(point.x() - self.points[0].x())),
|
|
|
+ 0,
|
|
|
+ self.mask.shape[1] - 1,
|
|
|
+ )
|
|
|
+ return self.mask[y, x]
|
|
|
return self.makePath().contains(point)
|
|
|
|
|
|
def getCircleRectFromLine(self, line):
|
|
@@ -294,7 +343,7 @@ class Shape(object):
|
|
|
return rectangle
|
|
|
|
|
|
def makePath(self):
|
|
|
- if self.shape_type == "rectangle":
|
|
|
+ if self.shape_type in ["rectangle", "mask"]:
|
|
|
path = QtGui.QPainterPath()
|
|
|
if len(self.points) == 2:
|
|
|
rectangle = self.getRectFromLine(*self.points)
|