[timeline] more bins, use trackingdata and hightlight user-labeled frames

This commit is contained in:
Jan Grewe 2025-02-21 09:48:13 +01:00
parent 98900ff480
commit 6f4ac1136b

View File

@ -5,9 +5,10 @@ from PySide6.QtCore import Qt
from PySide6.QtWidgets import QWidget, QVBoxLayout, QSizePolicy, QLabel from PySide6.QtWidgets import QWidget, QVBoxLayout, QSizePolicy, QLabel
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsItem, QGraphicsRectItem, QGraphicsLineItem from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsItem, QGraphicsRectItem, QGraphicsLineItem
from PySide6.QtCore import Qt, QRectF, QRectF from PySide6.QtCore import Qt, QRectF, QRectF
from PySide6.QtGui import QBrush, QColor, QPen, QFont from PySide6.QtGui import QBrush, QColor, QPen, QFont, QPainter
from fixtracks.utils.signals import DetectionTimelineSignals from fixtracks.utils.signals import DetectionTimelineSignals
from fixtracks.utils.trackingdata import TrackingData
class Window(QGraphicsRectItem): class Window(QGraphicsRectItem):
@ -40,7 +41,6 @@ class Window(QGraphicsRectItem):
self.signals.windowMoved.emit() self.signals.windowMoved.emit()
def setWindow(self, newx:float, newwidth:float): def setWindow(self, newx:float, newwidth:float):
def setWindow(self, newx: float, newwidth: float):
""" """
Update the window to the specified range. Update the window to the specified range.
Parameters Parameters
@ -53,11 +53,11 @@ class Window(QGraphicsRectItem):
------- -------
None None
""" """
logging.debug("timeline.window: update window to range %.5f to %.5f", newx, newwidth) logging.debug("timeline.window: update window to range %.5f to %.5f", newx, newwidth)
self._width = newwidth self._width = newwidth
r = self.rect() r = self.rect()
self.setRect(newx, r.y(), self._width, r.height()) self.setRect(newx, r.y(), self._width, r.height())
self.paint()
self.signals.windowMoved.emit() self.signals.windowMoved.emit()
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
@ -86,40 +86,44 @@ class Window(QGraphicsRectItem):
class DetectionTimeline(QWidget): class DetectionTimeline(QWidget):
signals = DetectionTimelineSignals() signals = DetectionTimelineSignals()
def __init__(self, detectiondata=None, trackone_id=1, tracktwo_id=2, parent=None): def __init__(self, trackone_id=1, tracktwo_id=2, parent=None):
super().__init__(parent) super().__init__(parent)
self._trackone = trackone_id self._trackone = trackone_id
self._tracktwo = tracktwo_id self._tracktwo = tracktwo_id
self._data = detectiondata self._data = None
self._rangeStart = 0.0 self._rangeStart = 0.0
self._rangeStop = 0.005 self._rangeStop = 0.005
self._total_width = 2000 self._total_width = 2000
self._stepCount = 300 self._stepCount = 1000
self._bg_brush = QBrush(QColor(20, 20, 20, 255)) self._bg_brush = QBrush(QColor(20, 20, 20, 255))
transparent_brush = QBrush(QColor(200, 200, 200, 64)) transparent_brush = QBrush(QColor(200, 200, 200, 64))
self._white_pen = QPen(QColor.fromString("white")) self._white_pen = QPen(QColor.fromString("white"))
self._white_pen.setWidth(0.1) self._white_pen.setWidth(0.1)
self._t1_pen = QPen(QColor.fromString("orange")) self._t1_pen = QPen(QColor.fromString("orange"))
self._t1_pen.setWidth(2) self._t1_pen.setWidth(1)
self._t2_pen = QPen(QColor(0, 255, 0, 255)) self._t2_pen = QPen(QColor(0, 255, 0, 255))
self._t2_pen.setWidth(2) self._t2_pen.setWidth(1)
self._other_pen = QPen(QColor.fromString("red")) self._other_pen = QPen(QColor.fromString("red"))
self._other_pen.setWidth(2) self._other_pen.setWidth(1)
axis_pen = QPen(QColor.fromString("white")) window_pen = QPen(QColor.fromString("white"))
axis_pen.setWidth(2) window_pen.setWidth(2)
self._user_brush = QBrush(QColor.fromString("white"))
user_pen = QPen(QColor.fromString("white"))
user_pen.setWidth(2)
font = QFont() font = QFont()
font.setPointSize(15) font.setPointSize(15)
font.setBold(False) font.setBold(True)
self._window = Window(0, 0, 100, 60, axis_pen, transparent_brush) self._window = Window(0, 0, 100, 60, window_pen, transparent_brush)
self._window.signals.windowMoved.connect(self.on_windowMoved) self._window.signals.windowMoved.connect(self.on_windowMoved)
self._scene = QGraphicsScene(QRectF(0, 0, self._total_width, 65.)) self._scene = QGraphicsScene(QRectF(0, 0, self._total_width, 85.))
self._scene.setBackgroundBrush(self._bg_brush) self._scene.setBackgroundBrush(self._bg_brush)
self._scene.addItem(self._window) self._scene.addItem(self._window)
self._view = QGraphicsView() self._view = QGraphicsView()
# self._view.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform); # self._view.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform)
self._view.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self._view.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self._view.setScene(self._scene) self._view.setScene(self._scene)
self._view.fitInView(self._scene.sceneRect(), aspectRadioMode=Qt.AspectRatioMode.KeepAspectRatio) self._view.fitInView(self._scene.sceneRect(), aspectRadioMode=Qt.AspectRatioMode.KeepAspectRatio)
@ -136,6 +140,10 @@ class DetectionTimeline(QWidget):
other_label.setFont(font) other_label.setFont(font)
other_label.setDefaultTextColor(self._other_pen.color()) other_label.setDefaultTextColor(self._other_pen.color())
other_label.setPos(200, 50) other_label.setPos(200, 50)
user_label = self._scene.addText("user-labeled", font)
user_label.setFont(font)
user_label.setDefaultTextColor(user_pen.color())
user_label.setPos(350, 50)
self._position_label = QLabel("") self._position_label = QLabel("")
f = self._position_label.font() f = self._position_label.font()
@ -146,13 +154,10 @@ class DetectionTimeline(QWidget):
layout.addWidget(self._view) layout.addWidget(self._view)
layout.addWidget(self._position_label, Qt.AlignmentFlag.AlignRight) layout.addWidget(self._position_label, Qt.AlignmentFlag.AlignRight)
self.setLayout(layout) self.setLayout(layout)
if self._data is not None:
self.draw_coverage()
# self.setMaximumHeight(100) # self.setMaximumHeight(100)
# self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) # self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
def setDetectionData(self, data): def setData(self, data:TrackingData):
self._data = data self._data = data
for i in self._scene.items(): for i in self._scene.items():
if isinstance(i, QGraphicsLineItem): if isinstance(i, QGraphicsLineItem):
@ -160,37 +165,29 @@ class DetectionTimeline(QWidget):
self.draw_coverage() self.draw_coverage()
def draw_coverage(self): def draw_coverage(self):
# FIXME this must be disentangled. timeline should not have to deal with two different ways of data storage if isinstance(self._data, TrackingData):
if isinstance(self._data, pd.DataFrame):
maxframe = np.max(self._data.frame.values)
bins = np.linspace(0, maxframe, self._stepCount)
pos = np.linspace(0, self._scene.width(), self._stepCount)
track1_frames = self._data.frame.values[self._data.track == self._trackone]
track2_frames = self._data.frame.values[self._data.track == self._tracktwo]
other_frames = self._data.frame.values[(self._data.track != self._trackone) &
(self._data.track != self._tracktwo)]
elif isinstance(self._data, dict):
maxframe = np.max(self._data["frame"]) maxframe = np.max(self._data["frame"])
bins = np.linspace(0, maxframe, self._stepCount) bins = np.linspace(0, maxframe, self._stepCount)
pos = np.linspace(0, self._scene.width(), self._stepCount) pos = np.linspace(0, self._scene.width(), self._stepCount) # of the vertical dashes is this correct?
track1_frames = self._data["frame"][self._data["track"] == self._trackone] track1_frames = self._data["frame"][self._data["track"] == self._trackone]
track2_frames = self._data["frame"][self._data["track"] == self._tracktwo] track2_frames = self._data["frame"][self._data["track"] == self._tracktwo]
other_frames = self._data["frame"][(self._data["track"] != self._trackone) & other_frames = self._data["frame"][(self._data["track"] != self._trackone) &
(self._data["track"] != self._tracktwo)] (self._data["track"] != self._tracktwo)]
userlabeled = self._data["frame"][self._data["userlabeled"]]
else: else:
print("Data is not trackingdata")
return return
t1_coverage, _ = np.histogram(track1_frames, bins=bins) t1_coverage, _ = np.histogram(track1_frames, bins=bins)
t1_coverage = t1_coverage > 0
t2_coverage, _ = np.histogram(track2_frames, bins=bins) t2_coverage, _ = np.histogram(track2_frames, bins=bins)
t2_coverage = t2_coverage > 0
other_coverage, _ = np.histogram(other_frames, bins=bins) other_coverage, _ = np.histogram(other_frames, bins=bins)
other_coverage = other_coverage > 0 labeled_coverage, _ = np.histogram(userlabeled, bins=bins)
for i in range(len(t1_coverage)-1): for i in range(len(bins)-1):
if t1_coverage[i]: self._scene.addLine(pos[i], 0, pos[i], 15., pen=self._t1_pen) if t1_coverage[i]: self._scene.addLine(pos[i], 0, pos[i], 15., pen=self._t1_pen)
if t2_coverage[i]: self._scene.addLine(pos[i], 17, pos[i], 32., pen=self._t2_pen) if t2_coverage[i]: self._scene.addLine(pos[i], 17, pos[i], 32., pen=self._t2_pen)
if other_coverage[i]: self._scene.addLine(pos[i], 34, pos[i], 49., pen=self._other_pen) if other_coverage[i]: self._scene.addLine(pos[i], 34, pos[i], 49., pen=self._other_pen)
if other_coverage[i]: self._scene.addLine(pos[i], 34, pos[i], 49., pen=self._other_pen)
if labeled_coverage[i]: self._scene.addEllipse(pos[i]-2, 52, 4, 4, brush=self._user_brush)
def updatePositionLabel(self): def updatePositionLabel(self):
start = np.round(self._rangeStart * 100, 4) start = np.round(self._rangeStart * 100, 4)
@ -207,6 +204,7 @@ class DetectionTimeline(QWidget):
def fit_scene_to_view(self): def fit_scene_to_view(self):
"""Scale the image to fit the QGraphicsView.""" """Scale the image to fit the QGraphicsView."""
logging.debug("Timeline: fit scene to view")
self._view.fitInView(self._scene.sceneRect(), Qt.KeepAspectRatio) self._view.fitInView(self._scene.sceneRect(), Qt.KeepAspectRatio)
def resizeEvent(self, event): def resizeEvent(self, event):
@ -289,6 +287,11 @@ def main():
view.setWindowPos(0.0) view.setWindowPos(0.0)
print(view.windowBounds()) print(view.windowBounds())
def as_dict(df):
d = {c: df[c].values for c in df.columns}
d["index"] = df.index.values
return d
import pickle import pickle
import numpy as np import numpy as np
from PySide6.QtWidgets import QApplication, QPushButton, QHBoxLayout from PySide6.QtWidgets import QApplication, QPushButton, QHBoxLayout
@ -298,13 +301,18 @@ def main():
datafile = PACKAGE_ROOT / "data/merged_small.pkl" datafile = PACKAGE_ROOT / "data/merged_small.pkl"
with open(datafile, "rb") as f: with open(datafile, "rb") as f:
df = pickle.load(f) df = pickle.load(f)
data = TrackingData()
data.setData(as_dict(df))
data.setUserSelection(np.arange(0,100, 1))
data.setAssignmentStatus(True)
start_x = 0.1 start_x = 0.1
app = QApplication([]) app = QApplication([])
window = QWidget() window = QWidget()
window.setMinimumSize(200, 75) window.setMinimumSize(200, 75)
view = DetectionTimeline(df) view = DetectionTimeline()
view.setData(data)
fwdBtn = QPushButton(">>") fwdBtn = QPushButton(">>")
fwdBtn.clicked.connect(lambda: fwd(0.5)) fwdBtn.clicked.connect(lambda: fwd(0.5))
zeroBtn = QPushButton("0->|") zeroBtn = QPushButton("0->|")