Files
sloth/sloth/items/items.py
T

556 lines
17 KiB
Python

from PyQt4.QtGui import *
from PyQt4.Qt import *
import logging
LOG = logging.getLogger(__name__)
class IgnorePrefix:
def __init__(self, value):
self.value = value
def __str__(self):
return self.value
class BaseItem(QAbstractGraphicsShapeItem):
"""
Base class for visualization items.
"""
cycleValuesOnKeypress = {}
defaultAutoTextKeys = []
def __init__(self, model_item=None, prefix="", parent=None):
"""
Creates a visualization item.
Parameters
==========
index :
data :
parent :
"""
QAbstractGraphicsShapeItem.__init__(self, parent)
self.setFlags(QGraphicsItem.ItemIsSelectable |
QGraphicsItem.ItemIsMovable |
QGraphicsItem.ItemSendsGeometryChanges |
QGraphicsItem.ItemSendsScenePositionChanges)
self._model_item = model_item
if self._model_item is not None:
self._model_item.model().dataChanged.connect(self.onDataChanged)
# initialize members
self._prefix = prefix
self._auto_text_keys = self.defaultAutoTextKeys[:]
self._text = ""
self._text_bg_brush = None
self._text_item = QGraphicsTextItem(self)
self._text_item.setPos(0, 0)
self._text_item.setAcceptHoverEvents(False)
self._text_item.setFlags(QGraphicsItem.ItemIgnoresTransformations)
self._text_item.setHtml(self._compile_text())
self._valid = True
self.changeColor()
def changeColor(self):
if self._model_item is not None:
c = self._model_item.getColor()
if c is not None:
self.setColor(c)
return
self.setColor(Qt.yellow)
def onDataChanged(self, indexFrom, indexTo):
# FIXME why is this not updated, when changed graphically via attribute box ?
#print "onDataChanged", self._model_item.index(), indexFrom, indexTo, indexFrom.parent()
if indexFrom == self._model_item.index():
self.changeColor()
#print "hit"
# self._text_item.setHtml(self._compile_text())
def modelItem(self):
"""
Returns the model item of this items.
"""
return self._model_item
def index(self):
"""
Returns the index of this item.
"""
return self._model_item.index()
def prefix(self):
"""
Returns the key prefix of the item.
"""
return self._prefix
def setPen(self, pen):
QAbstractGraphicsShapeItem.setPen(self, pen)
self._text_item.setDefaultTextColor(pen)
def setText(self, text=""):
"""
Sets a text to be displayed on this item.
"""
self._text = text
self._text_item.setHtml(self._compile_text())
def text(self):
return self._text
def setTextBackgroundBrush(self, brush=None):
"""
Sets the brush to be used to fill the background region
behind the text. Set to None to not draw a background
(leave transparent).
"""
self._text_bg_brush = brush
def textBackgroundBrush(self):
"""
Returns the background brush for the text region.
"""
return self._text_bg_brush
def setAutoTextKeys(self, keys=[]):
"""
Sets the keys for which the values from the annotations
are displayed automatically as text.
"""
self._auto_text_keys = keys
self._text_item.setHtml(self._compile_text())
def autoTextKeys(self):
"""
Returns the list of keys for which the values from
the annotations are displayed as text automatically.
"""
return self._auto_text_keys
def isValid(self):
"""
Return whether this graphics item is valid, i.e. has
a matching, valid model item connected to it. An item is
by default valid, will only be set invalid on failure.
"""
return self._valid
def setValid(self, val):
self._valid = val
def _compile_text(self):
text_lines = []
if self._text != "" and self._text is not None:
text_lines.append(self._text)
for key in self._auto_text_keys:
text_lines.append("%s: %s" % \
(key, self._model_item.get(key, "")))
return '<br/>'.join(text_lines)
def dataChanged(self):
self.dataChange()
self._text_item.setHtml(self._compile_text())
self.update()
def dataChange(self):
pass
def updateModel(self, ann=None):
if ann is not None:
self._model_item.update(ann)
def boundingRect(self):
return QRectF(0, 0, 0, 0)
def setColor(self, color):
self.setPen(color)
self.setBrush(color)
self.update()
def paint(self, painter, option, widget=None):
pass
def itemChange(self, change, value):
if change == QGraphicsItem.ItemPositionHasChanged:
self.updateModel()
return QAbstractGraphicsShapeItem.itemChange(self, change, value)
def keyPressEvent(self, event):
"""
This handles the value cycling as defined in cycleValuesOnKeypress.
"""
if str(event.text()) in self.cycleValuesOnKeypress:
itemkey, valuelist = self.cycleValuesOnKeypress[str(event.text())]
if isinstance(itemkey, IgnorePrefix):
itemkey = itemkey.value
else:
itemkey = self.prefix() + itemkey
if len(valuelist) > 0:
oldvalue = self._model_item.get(itemkey, None)
if oldvalue is None:
nextindex = 0
else:
try:
nextindex = valuelist.index(oldvalue) + 1
nextindex %= len(valuelist)
except ValueError:
nextindex = 0
newvalue = valuelist[nextindex]
if newvalue is None and oldvalue is not None:
self._model_item.delete(itemkey)
else:
self._model_item[itemkey] = valuelist[nextindex]
self.dataChanged()
event.accept()
class PointItem(BaseItem):
"""
Visualization item for points.
"""
def __init__(self, model_item=None, prefix="", parent=None):
BaseItem.__init__(self, model_item, prefix, parent)
self._radius = 2
self._point = None
self.updatePoint()
def setRadius(self, radius):
self.prepareGeometryChange()
self._radius = radius
self.update()
def radius(self):
return self._radius
def __call__(self, model_item=None, parent=None):
pointitem = PointItem(model_item, parent)
pointitem.setPen(self.pen())
pointitem.setBrush(self.brush())
pointitem.setRadius(self._radius)
return pointitem
def dataChange(self):
self.updatePoint()
def updateModel(self):
self._model_item.update({
self.prefix() + 'x': self.scenePos().x(),
self.prefix() + 'y': self.scenePos().y(),
})
def updatePoint(self):
if self._model_item is None:
return
try:
point = QPointF(float(self._model_item[self.prefix() + 'x']),
float(self._model_item[self.prefix() + 'y']))
except KeyError as e:
LOG.error("PointItem: Could not find expected key in item: "
+ str(e) + ". Check your config!")
self.setValid(False)
self._point = None
return
if point == self._point:
return
self.prepareGeometryChange()
self._point = point
self.setPos(self._point)
def boundingRect(self):
r = self._radius
return QRectF(-r, -r, 2 * r, 2 * r)
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.drawEllipse(self.boundingRect())
def keyPressEvent(self, event):
BaseItem.keyPressEvent(self, event)
step = 1
if event.modifiers() & Qt.ShiftModifier:
step = 5
ds = {Qt.Key_Left: (-step, 0),
Qt.Key_Right: (step, 0),
Qt.Key_Up: (0, -step),
Qt.Key_Down: (0, step)
}.get(event.key(), None)
if ds is not None:
self.moveBy(*ds)
event.accept()
class RectItem(BaseItem):
def __init__(self, model_item=None, prefix="", parent=None):
BaseItem.__init__(self, model_item, prefix, parent)
self._rect = None
self._resize = False
self._resize_start = None
self._resize_start_rect = None
self._updateRect(self._dataToRect(self._model_item))
LOG.debug("Constructed rect %s for model item %s" %
(self._rect, model_item))
def __call__(self, model_item=None, parent=None):
item = RectItem(model_item, parent)
item.setPen(self.pen())
item.setBrush(self.brush())
return item
def _dataToRect(self, model_item):
if model_item is None:
return QRectF()
try:
return QRectF(float(model_item[self.prefix() + 'x']),
float(model_item[self.prefix() + 'y']),
float(model_item[self.prefix() + 'width']),
float(model_item[self.prefix() + 'height']))
except KeyError as e:
LOG.error("PointItem: Could not find expected key in item: "
+ str(e) + ". Check your config!")
self.setValid(False)
return QRectF()
def _updateRect(self, rect):
if rect == self._rect:
return
self.prepareGeometryChange()
self._rect = rect
self.setPos(rect.topLeft())
def updateModel(self):
self._rect = QRectF(self.scenePos(), self._rect.size())
self._model_item.update({
self.prefix() + 'x': float(self._rect.topLeft().x()),
self.prefix() + 'y': float(self._rect.topLeft().y()),
self.prefix() + 'width': float(self._rect.width()),
self.prefix() + 'height': float(self._rect.height()),
})
def boundingRect(self):
return QRectF(QPointF(0, 0), self._rect.size())
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())
def dataChange(self):
rect = self._dataToRect(self._model_item)
self._updateRect(rect)
def mousePressEvent(self, event):
#if event.modifiers() & Qt.ControlModifier != 0:
if event.button() & Qt.RightButton != 0:
self._resize = True
self._resize_start = event.scenePos()
self._resize_start_rect = QRectF(self._rect)
event.accept()
else:
BaseItem.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if self._resize:
diff = event.scenePos() - self._resize_start
tl = self._resize_start_rect.topLeft()
br = self._resize_start_rect.bottomRight()
rect = QRectF(tl, br + diff).normalized()
self._updateRect(rect)
self.updateModel()
event.accept()
else:
BaseItem.mouseMoveEvent(self, event)
def mouseReleaseEvent(self, event):
if self._resize:
self._resize = False
event.accept()
else:
BaseItem.mouseReleaseEvent(self, event)
def keyPressEvent(self, event):
BaseItem.keyPressEvent(self, event)
step = 1
if event.modifiers() & Qt.ShiftModifier:
step = 5
ds = {Qt.Key_Left: (-step, 0),
Qt.Key_Right: (step, 0),
Qt.Key_Up: (0, -step),
Qt.Key_Down: (0, step),
}.get(event.key(), None)
if ds is not None:
if event.modifiers() & Qt.ControlModifier:
rect = self._rect.adjusted(*((0, 0) + ds))
else:
rect = self._rect.adjusted(*(ds + ds))
self._updateRect(rect)
self.updateModel()
event.accept()
class GroupItem(BaseItem):
items = []
def __init__(self, model_item=None, prefix="", parent=None):
self._children = []
BaseItem.__init__(self, model_item, prefix, parent)
self.setFlag(QGraphicsItem.ItemIsMovable, False)
self.setFlag(QGraphicsItem.ItemIsSelectable, False)
self.setFlag(QGraphicsItem.ItemIsFocusable, False)
self.createChildren()
def createChildren(self):
for callable_, prefix in self.items:
child = callable_(self._model_item, prefix, self)
self._children.append(child)
def setColor(self, *args, **kwargs):
for c in self._children:
c.setColor(*args, **kwargs)
BaseItem.setColor(self, *args, **kwargs)
def boundingRect(self):
br = QRectF()
for item in self.childItems():
if item is self._text_item:
continue
br |= item.mapRectToParent(item.boundingRect())
return br
class OccludablePointItem(PointItem):
cycleValuesOnKeypress = {
'o': ('occluded', [True, False])
}
def __init__(self, *args, **kwargs):
PointItem.__init__(self, *args, **kwargs)
self.updateColor()
def dataChange(self):
PointItem.dataChange(self)
self.updateColor()
def updateColor(self):
key = self.prefix() + 'occluded'
if key in self._model_item:
occluded = self._model_item[key]
self.setColor(Qt.red if occluded else Qt.yellow)
class IDRectItem(RectItem):
cycleValuesOnKeypress = dict(
[('i', (IgnorePrefix('id'), range(36)))] +
[(str(i), (IgnorePrefix('id'), [i])) for i in range(10)] +
[(chr(i-10+65).lower(), (IgnorePrefix('id'), [i])) for i in range(10, 36)]
)
defaultAutoTextKeys = ['id']
class BBoxFaceItem(GroupItem):
items = [
(IDRectItem, "bbox"),
(OccludablePointItem, "lec"),
(OccludablePointItem, "rec"),
(OccludablePointItem, "mc"),
]
class ControlItem(QGraphicsItem):
def __init__(self, parent=None):
QGraphicsItem.__init__(self, parent)
# always have the same size
self.setFlags(QGraphicsItem.ItemIgnoresTransformations)
def paint(self, painter, option, widget=None):
color = QColor('black')
color.setAlpha(200)
painter.fillRect(self.boundingRect(), color)
class NPointFacePointItem(QGraphicsEllipseItem):
def __init__(self, landmark, *args, **kwargs):
self._landmark = landmark
QGraphicsEllipseItem.__init__(self, *args, **kwargs)
self.setFlags(QGraphicsItem.ItemIsMovable |
QGraphicsItem.ItemSendsGeometryChanges |
QGraphicsItem.ItemSendsScenePositionChanges)
def landmark(self):
return self._landmark
def itemChange(self, change, value):
if change == QGraphicsItem.ItemPositionHasChanged:
parent = self.parentItem()
if parent is not None:
parent.landmarkChanged(self, value)
return QAbstractGraphicsShapeItem.itemChange(self, change, value)
def setColor(self, color):
self.setPen(color)
self.update()
class NPointFaceItem(GroupItem):
items = [
(OccludablePointItem, "leoc"),
(OccludablePointItem, "lec"),
(OccludablePointItem, "leic"),
(OccludablePointItem, "reic"),
(OccludablePointItem, "rec"),
(OccludablePointItem, "reoc"),
(OccludablePointItem, "nt"),
(OccludablePointItem, "ulc"),
(OccludablePointItem, "mc"),
(OccludablePointItem, "lmc"),
(OccludablePointItem, "rmc"),
]
def __init__(self, model_item=None, prefix="", parent=None):
GroupItem.__init__(self, model_item, prefix, parent)
def createChildren(self):
for callable_, prefix in self.items:
if prefix + 'x' in self._model_item and \
prefix + 'y' in self._model_item:
child = callable_(self._model_item, prefix, self)
self._children.append(child)
def boundingRect(self):
br = GroupItem.boundingRect(self)
offset = 0.2 * br.height()
return br.adjusted(-offset, -offset, +offset, +offset)
def paint(self, painter, option, widget=None):
GroupItem.paint(self, painter, option, widget)
pen = self.pen()
if self.isSelected():
pen.setStyle(Qt.DashLine)
painter.setPen(pen)
painter.drawRect(self.boundingRect())