Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions sloth/conf/default_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This is sloth's default configuration.
#
# The configuration file is a simple python module with module-level
# variables. This module contains the default values for sloth's
# variables. This module contains the default values for sloth's
# configuration variables.
#
# In all cases in the configuration where a python callable (such as a
Expand All @@ -16,16 +16,16 @@
# be one dictionary that contains the following keys:
#
# - 'item' : Visualization item for this label. This can be
# any python callable or a module path string
# any python callable or a module path string
# implementing the visualization item interface.
#
# - 'inserter' : (optional) Item inserter for this label.
# If the user selects to insert a new label of this class
# the inserter is responsible to actually
# the inserter is responsible to actually
# capture the users mouse actions and insert
# a new label into the annotation model.
#
# - 'hotkey' : (optional) A keyboard shortcut starting
# - 'hotkey' : (optional) A keyboard shortcut starting
# the insertion of a new label of this class.
#
# - 'attributes' : (optional) A dictionary that defines the
Expand All @@ -52,6 +52,15 @@
'hotkey': 'r',
'text': 'Rectangle',
},
{
'attributes': {
'class': 'extreme_clicks',
},
'inserter': 'sloth.items.ExtremeClickingInserter',
'item': 'sloth.items.ExtremeClickingItem',
'hotkey': 'e',
'text': 'Extreme Clicks',
},
{
'attributes': {
'class': 'point',
Expand All @@ -78,7 +87,7 @@
# with at least 2 entries, where the first entry is the hotkey (sequence),
# and the second entry is the function that is called. The function
# should expect a single parameter, the labeltool object. The optional
# third entry -- if present -- is expected to be a string describing the
# third entry -- if present -- is expected to be a string describing the
# action.
HOTKEYS = (
('Space', [lambda lt: lt.currentImage().confirmAll(),
Expand Down Expand Up @@ -115,9 +124,7 @@
# PLUGINS
#
# A list/tuple of classes implementing the sloth plugin interface. The
# classes can either be given directly or their module path be specified
# classes can either be given directly or their module path be specified
# as string.
PLUGINS = (
)


91 changes: 91 additions & 0 deletions sloth/items/inserters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import math
import sys
from PyQt4.QtGui import *
from PyQt4.Qt import *

Expand Down Expand Up @@ -78,6 +79,96 @@ def mousePressEvent(self, event, image_item):
self.annotationFinished.emit()
event.accept()

class ExtremeClickingInserter(ItemInserter):
def __init__(self, labeltool, scene, default_properties=None,
prefix="", commit=True):
ItemInserter.__init__(self, labeltool, scene, default_properties,
prefix, commit)

self._helpLines = None
self._helpLinesPen = QPen(Qt.green, 2, Qt.DashLine)

self._points = QGraphicsItemGroup()
self._scene.addItem(self._points)
self._pointPen = QPen(Qt.red)

def mouseReleaseEvent(self, event, image_item):
pos = event.scenePos()
new_point = QGraphicsEllipseItem(QRectF(pos.x()-2, pos.y()-2, 5, 5))
new_point.setPen(self._pointPen)
self._points.addToGroup(new_point)

if len(self._points.childItems()) == 4:
left = QPointF(sys.maxint, 0)
right = QPointF(-sys.maxint, 0)
top = QPointF(0, sys.maxint)
bottom = QPointF(0, -sys.maxint)
for point in self._points.childItems():
if point.rect().x() < left.x():
left = point.rect()
if point.rect().y() < top.y():
top = point.rect()
if (point.rect().x() + point.rect().width()) > right.x():
right = point.rect()
if (point.rect().y() + point.rect().height()) > bottom.y():
bottom = point.rect()

width = right.x() - left.x()
height = bottom.y() - top.y()

if width > 1 and height > 1:
self._ann.update({self._prefix + 'left_x': left.x(),
self._prefix + 'left_y': left.y(),
self._prefix + 'right_x': right.x(),
self._prefix + 'right_y': right.y(),
self._prefix + 'top_x': top.x(),
self._prefix + 'top_y': top.y(),
self._prefix + 'bottom_x': bottom.x(),
self._prefix + 'bottom_y': bottom.y()})
self._ann.update(self._default_properties)
if self._commit:
image_item.addAnnotation(self._ann)
self.annotationFinished.emit()

self._scene.removeItem(self._points)
self._points = QGraphicsItemGroup()
self._scene.addItem(self._points)

event.accept()

def mouseMoveEvent(self, event, image_item):
if self._helpLines is not None:
self._scene.removeItem(self._helpLines)

self._helpLines = QGraphicsItemGroup()
group = self._helpLines

verticalHelpLine = QGraphicsLineItem(event.scenePos().x(), 0, event.scenePos().x(), self._scene.height())
horizontalHelpLine = QGraphicsLineItem(0, event.scenePos().y(), self._scene.width(), event.scenePos().y())

horizontalHelpLine.setPen(self._helpLinesPen)
verticalHelpLine.setPen(self._helpLinesPen)

group.addToGroup(verticalHelpLine)
group.addToGroup(horizontalHelpLine)

self._scene.addItem(self._helpLines)

event.accept()

def allowOutOfSceneEvents(self):
return True

def abort(self):
if self._helpLines is not None:
self._scene.removeItem(self._helpLines)
self._helpLines = None

self._scene.removeItem(self._points)
self._points = QGraphicsItemGroup()
self._scene.addItem(self._points)

ItemInserter.abort(self)

class RectItemInserter(ItemInserter):
def __init__(self, labeltool, scene, default_properties=None,
Expand Down
151 changes: 150 additions & 1 deletion sloth/items/items.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from PyQt4.Qt import *

import numpy as np
import sys

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -346,6 +347,154 @@ def keyPressEvent(self, event):
self.moveBy(*ds)
event.accept()

class ExtremeClickingItem(BaseItem):
def __init__(self, model_item=None, prefix="", parent=None):
BaseItem.__init__(self, model_item, prefix, parent)

self.setFlags(QGraphicsItem.ItemIsSelectable | \
QGraphicsItem.ItemSendsGeometryChanges | \
QGraphicsItem.ItemSendsScenePositionChanges)

self._left_point = None
self._right_point = None
self._top_point = None
self._bottom_point = None

self._move_point = None
self._move_x_key = None
self._move_y_key = None
self._move_limit_x = None
self._move_limit_y = None

self._updatePoints(model_item)

def __call__(self, model_item=None, parent=None):
item = ExtremeClickingItem(model_item, parent)
item.setPen(self.pen())
item.setBrush(self.brush())
return item


def _updatePoints(self, model_item):
self.prepareGeometryChange()
if model_item is not None:
try:
self._left_point = QPointF(float(model_item[self.prefix() + "left_x"]),
float(model_item[self.prefix() + "left_y"]))
self._right_point = QPointF(float(model_item[self.prefix() + "right_x"]),
float(model_item[self.prefix() + "right_y"]))
self._top_point = QPointF(float(model_item[self.prefix() + "top_x"]),
float(model_item[self.prefix() + "top_y"]))
self._bottom_point = QPointF(float(model_item[self.prefix() + "bottom_x"]),
float(model_item[self.prefix() + "bottom_y"]))
except KeyError as e:
LOG.debug("ExtremeClickItem: Could not find expected key in item: "
+ str(e) + ". Check your config!")
self.setValid(False)
else:
self._left_point = QPointF()
self._right_point = QPointF()
self._top_point = QPointF()
self._bottom_point = QPointF()
self.setPos(QPointF(0, 0))

def _buildRect(self):
x_min = self._left_point.x()
y_min = self._top_point.y()
x_max = self._right_point.x()
y_max = self._bottom_point.y()
width = x_max - x_min
height = y_max - y_min
return QRectF(QPointF(x_min, y_min), QSizeF(width, height))

def boundingRect(self):
return self._buildRect()

def _drawPoint(self, painter, point):
painter.drawEllipse(QRectF(point.x()-2, point.y()-2, 5, 5))

def paint(self, painter, option, widget=None):
BaseItem.paint(self, painter, option, widget)

pen = self.pen()
if self.isSelected():
pen.setStyle(Qt.DashLine)
painter.setPen(pen)
painter.drawRect(self.boundingRect())
self._drawPoint(painter, self._left_point)
self._drawPoint(painter, self._right_point)
self._drawPoint(painter, self._top_point)
self._drawPoint(painter, self._bottom_point)

def dataChange(self):
self._updatePoints(self._model_item)

def mousePressEvent(self, event):
#if event.modifiers() & Qt.ControlModifier != 0:
if event.button() & Qt.RightButton != 0:
rect = self.boundingRect()
dist_left = abs(event.scenePos().x() - rect.x())
dist_right = abs(event.scenePos().x() - (rect.x() + rect.width()))
dist_top = abs(event.scenePos().y() - rect.y())
dist_bottom = abs(event.scenePos().y() - (rect.y() + rect.height()))

# find nearest rect border
np_dists = np.array([dist_left, dist_right, dist_top, dist_bottom])
argmin_dist = np_dists.argmin()

np_points = np.array([[self._left_point.x(), self._left_point.y()],
[self._right_point.x(), self._right_point.y()],
[self._top_point.x(), self._top_point.y()],
[self._bottom_point.x(), self._bottom_point.y()]])
np_other_points = np_points[[i != argmin_dist for i in range(4)], :]

if argmin_dist == 0:
self._move_point = self._left_point
point_prefix = 'left'
self._move_limit_x = (-sys.maxint, np_other_points[:, 0].min())
self._move_limit_y = (self._top_point.y(), self._bottom_point.y())
elif argmin_dist == 1:
self._move_point = self._right_point
point_prefix = 'right'
self._move_limit_x = (np_other_points[:, 0].max(), sys.maxint)
self._move_limit_y = (self._top_point.y(), self._bottom_point.y())
elif argmin_dist == 2:
self._move_point = self._top_point
point_prefix = 'top'
self._move_limit_x = (self._left_point.x(), self._right_point.x())
self._move_limit_y = (-sys.maxint, np_other_points[:, 1].min())
else:
self._move_point = self._bottom_point
point_prefix = 'bottom'
self._move_limit_x = (self._left_point.x(), self._right_point.x())
self._move_limit_y = (np_other_points[:, 1].max(), sys.maxint)

self._move_x_key = point_prefix + '_x'
self._move_y_key = point_prefix + '_y'

event.accept()
else:
BaseItem.mousePressEvent(self, event)

def mouseMoveEvent(self, event):
if self._move_point is not None:
new_x = min(max(event.scenePos().x(), self._move_limit_x[0]), self._move_limit_x[1])
new_y = min(max(event.scenePos().y(), self._move_limit_y[0]), self._move_limit_y[1])
self._move_point.setX(new_x)
self._move_point.setY(new_y)
self._model_item[self._move_x_key] = self._move_point.x()
self._model_item[self._move_y_key] = self._move_point.y()
self._updatePoints(self._model_item)
event.accept()
else:
BaseItem.mouseMoveEvent(self, event)

def mouseReleaseEvent(self, event):
if self._move_point is not None:
self._move_point = None
event.accept()
else:
BaseItem.mouseReleaseEvent(self, event)

class RectItem(BaseItem):
def __init__(self, model_item=None, prefix="", parent=None):
Expand Down