diff --git a/fixtracks/widgets/detectiontimeline.py b/fixtracks/widgets/detectiontimeline.py index b69ac94..a7c6a51 100644 --- a/fixtracks/widgets/detectiontimeline.py +++ b/fixtracks/widgets/detectiontimeline.py @@ -5,9 +5,10 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import QWidget, QVBoxLayout, QSizePolicy, QLabel from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsItem, QGraphicsRectItem, QGraphicsLineItem 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.trackingdata import TrackingData class Window(QGraphicsRectItem): @@ -40,24 +41,23 @@ class Window(QGraphicsRectItem): self.signals.windowMoved.emit() def setWindow(self, newx:float, newwidth:float): - def setWindow(self, newx: float, newwidth: float): - """ - Update the window to the specified range. - Parameters - ---------- - newx : float - The new x-coordinate of the window. - newwidth : float - The new width of the window. - Returns - ------- - None - """ - + """ + Update the window to the specified range. + Parameters + ---------- + newx : float + The new x-coordinate of the window. + newwidth : float + The new width of the window. + Returns + ------- + None + """ logging.debug("timeline.window: update window to range %.5f to %.5f", newx, newwidth) self._width = newwidth r = self.rect() self.setRect(newx, r.y(), self._width, r.height()) + self.paint() self.signals.windowMoved.emit() def mouseMoveEvent(self, event): @@ -86,40 +86,44 @@ class Window(QGraphicsRectItem): class DetectionTimeline(QWidget): 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) self._trackone = trackone_id self._tracktwo = tracktwo_id - self._data = detectiondata + self._data = None self._rangeStart = 0.0 self._rangeStop = 0.005 self._total_width = 2000 - self._stepCount = 300 + self._stepCount = 1000 self._bg_brush = QBrush(QColor(20, 20, 20, 255)) transparent_brush = QBrush(QColor(200, 200, 200, 64)) self._white_pen = QPen(QColor.fromString("white")) self._white_pen.setWidth(0.1) 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.setWidth(2) + self._t2_pen.setWidth(1) self._other_pen = QPen(QColor.fromString("red")) - self._other_pen.setWidth(2) - axis_pen = QPen(QColor.fromString("white")) - axis_pen.setWidth(2) + self._other_pen.setWidth(1) + window_pen = QPen(QColor.fromString("white")) + window_pen.setWidth(2) + self._user_brush = QBrush(QColor.fromString("white")) + user_pen = QPen(QColor.fromString("white")) + user_pen.setWidth(2) + font = QFont() 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._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.addItem(self._window) 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.setScene(self._scene) self._view.fitInView(self._scene.sceneRect(), aspectRadioMode=Qt.AspectRatioMode.KeepAspectRatio) @@ -136,6 +140,10 @@ class DetectionTimeline(QWidget): other_label.setFont(font) other_label.setDefaultTextColor(self._other_pen.color()) 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("") f = self._position_label.font() @@ -146,13 +154,10 @@ class DetectionTimeline(QWidget): layout.addWidget(self._view) layout.addWidget(self._position_label, Qt.AlignmentFlag.AlignRight) self.setLayout(layout) - - if self._data is not None: - self.draw_coverage() # self.setMaximumHeight(100) # self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - def setDetectionData(self, data): + def setData(self, data:TrackingData): self._data = data for i in self._scene.items(): if isinstance(i, QGraphicsLineItem): @@ -160,37 +165,29 @@ class DetectionTimeline(QWidget): self.draw_coverage() 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, 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): + if isinstance(self._data, TrackingData): maxframe = np.max(self._data["frame"]) 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] track2_frames = self._data["frame"][self._data["track"] == self._tracktwo] other_frames = self._data["frame"][(self._data["track"] != self._trackone) & (self._data["track"] != self._tracktwo)] + userlabeled = self._data["frame"][self._data["userlabeled"]] else: + print("Data is not trackingdata") return t1_coverage, _ = np.histogram(track1_frames, bins=bins) - t1_coverage = t1_coverage > 0 t2_coverage, _ = np.histogram(track2_frames, bins=bins) - t2_coverage = t2_coverage > 0 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 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 labeled_coverage[i]: self._scene.addEllipse(pos[i]-2, 52, 4, 4, brush=self._user_brush) def updatePositionLabel(self): start = np.round(self._rangeStart * 100, 4) @@ -207,6 +204,7 @@ class DetectionTimeline(QWidget): def fit_scene_to_view(self): """Scale the image to fit the QGraphicsView.""" + logging.debug("Timeline: fit scene to view") self._view.fitInView(self._scene.sceneRect(), Qt.KeepAspectRatio) def resizeEvent(self, event): @@ -289,6 +287,11 @@ def main(): view.setWindowPos(0.0) 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 numpy as np from PySide6.QtWidgets import QApplication, QPushButton, QHBoxLayout @@ -298,13 +301,18 @@ def main(): datafile = PACKAGE_ROOT / "data/merged_small.pkl" with open(datafile, "rb") as 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 app = QApplication([]) window = QWidget() window.setMinimumSize(200, 75) - view = DetectionTimeline(df) + view = DetectionTimeline() + view.setData(data) + fwdBtn = QPushButton(">>") fwdBtn.clicked.connect(lambda: fwd(0.5)) zeroBtn = QPushButton("0->|")