From 7586643faade406c6e828e24a91316fd453956cb Mon Sep 17 00:00:00 2001 From: Jan Grewe Date: Fri, 24 Jan 2025 17:21:34 +0100 Subject: [PATCH] work work --- fixtracks/dialogs/previewdialog.py | 4 +- fixtracks/utils/signals.py | 12 +- fixtracks/widgets/centralwidget.py | 3 +- fixtracks/widgets/detectionmerge.py | 12 +- fixtracks/widgets/mergepreview.py | 12 +- fixtracks/widgets/taskwidget.py | 4 - fixtracks/widgets/taskwidgets.py | 19 --- fixtracks/widgets/tracks.py | 192 ++++++++++++++++++++++++++++ 8 files changed, 218 insertions(+), 40 deletions(-) delete mode 100644 fixtracks/widgets/taskwidgets.py create mode 100644 fixtracks/widgets/tracks.py diff --git a/fixtracks/dialogs/previewdialog.py b/fixtracks/dialogs/previewdialog.py index 532183f..f625c3c 100644 --- a/fixtracks/dialogs/previewdialog.py +++ b/fixtracks/dialogs/previewdialog.py @@ -47,7 +47,7 @@ class PreviewDialog(QDialog): imgfile = file_dialog.selectedFiles()[0] if imgfile is not None: img = QImage(imgfile) - self.merge_preview.set_image(img) + self.merge_preview.setImage(img) file_dialog = QFileDialog(self, "Select pickled DataFrame", "", "Pandas DataFrame (*.pkl)") if file_dialog.exec(): @@ -63,7 +63,7 @@ class PreviewDialog(QDialog): self._progress_bar.setValue(0) if state and self._reader is not None: self._df = self._reader.data - self.merge_preview.set_dataframe(self._df) + self.merge_preview.setDataframe(self._df) self._reader = None diff --git a/fixtracks/utils/signals.py b/fixtracks/utils/signals.py index 12f619c..b94a7a6 100644 --- a/fixtracks/utils/signals.py +++ b/fixtracks/utils/signals.py @@ -1,4 +1,4 @@ -from PySide6.QtCore import Signal, QObject +from PySide6.QtCore import Signal, QObject, QPointF class ProducerSignals(QObject): finished = Signal(bool) @@ -7,3 +7,13 @@ class ProducerSignals(QObject): # running = pyqtSignal() progress = Signal(float) progress2 = Signal((str, float, float)) + +class DetectionSignals(QObject): + hover = Signal((int, QPointF)) + clicked = Signal((int, QPointF)) + +class TimelineSignals(QObject): + changed = Signal(int) + +class ControlSignals(QPointF): + test = Signal(int) \ No newline at end of file diff --git a/fixtracks/widgets/centralwidget.py b/fixtracks/widgets/centralwidget.py index 5af35f4..7da22f7 100644 --- a/fixtracks/widgets/centralwidget.py +++ b/fixtracks/widgets/centralwidget.py @@ -2,7 +2,7 @@ import logging from PySide6.QtWidgets import QWidget, QStackedLayout, QSizePolicy from PySide6.QtCore import Qt -from fixtracks.widgets.taskwidgets import FixTracks +from fixtracks.widgets.tracks import FixTracks from fixtracks.widgets.detectionmerge import MergeDetections from fixtracks.widgets.taskwidget import TasksWidget from fixtracks.widgets.converter import Json2PandasConverter @@ -23,6 +23,7 @@ class CentralWidget(QWidget): self._convertwidget.back.connect(self._on_back) self._trackwidget = FixTracks(self) + self._trackwidget.back.connect(self._on_back) layout = QStackedLayout() layout.setAlignment(Qt.AlignmentFlag.AlignCenter) diff --git a/fixtracks/widgets/detectionmerge.py b/fixtracks/widgets/detectionmerge.py index e073809..056ed09 100644 --- a/fixtracks/widgets/detectionmerge.py +++ b/fixtracks/widgets/detectionmerge.py @@ -22,7 +22,7 @@ class VideoPreview(QWidget): layout.addWidget(self._view) self.setLayout(layout) - def set_image(self, img): + def setImage(self, img): self._image = img self._scene = QGraphicsScene() self._pixmapitem = self._scene.addPixmap(QPixmap.fromImage(img)) @@ -36,7 +36,7 @@ class VideoPreview(QWidget): self._scene.addLine(start_x, start_y, start_x, end_y, self._line_pen) self._view.show() - def set_lineposition(self, position): + def setLineposition(self, position): image_rect = self._pixmapitem.boundingRect() for i in self._scene.items(): if isinstance(i, QGraphicsLineItem): @@ -206,7 +206,7 @@ class MergeDetections(QWidget): frame = self.right_imagereader.frame if frame is not None: img = self._toImage(frame) - self.right_preview.set_image(img) + self.right_preview.setImage(img) self.right_posspinner.setMaximum(img.width() - 1) self.right_posspinner.setValue(0) self.checkButtons() @@ -224,7 +224,7 @@ class MergeDetections(QWidget): frame = self.left_imagereader.frame if frame is not None: img = self._toImage(frame) - self.left_preview.set_image(img) + self.left_preview.setImage(img) self.left_posspinner.setMaximum(img.width() - 1) self.left_posspinner.setValue(img.width() - 1) self.checkButtons() @@ -260,10 +260,10 @@ class MergeDetections(QWidget): self.checkButtons() def on_leftmergelinemove(self): - self.left_preview.set_lineposition(self.left_posspinner.value()) + self.left_preview.setLineposition(self.left_posspinner.value()) def on_rightmergelinemove(self): - self.right_preview.set_lineposition(self.right_posspinner.value()) + self.right_preview.setLineposition(self.right_posspinner.value()) def on_mergePreview(self): logging.debug("detectionmerge: mergePreview pressed") diff --git a/fixtracks/widgets/mergepreview.py b/fixtracks/widgets/mergepreview.py index 357b1e2..c0007da 100644 --- a/fixtracks/widgets/mergepreview.py +++ b/fixtracks/widgets/mergepreview.py @@ -1,12 +1,10 @@ import logging import numpy as np -from PySide6.QtWidgets import QWidget, QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QSizePolicy, QSpinBox, QGraphicsView, QGraphicsScene, QGraphicsLineItem, QSpacerItem, QProgressDialog, QFileDialog, QSlider +from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QSizePolicy, QSpinBox, QGraphicsView, QGraphicsScene, QSpacerItem, QSlider from PySide6.QtCore import Qt from PySide6.QtGui import QPixmap, QBrush, QColor -from fixtracks.utils.reader import ImageReader - class MergePreview(QWidget): @@ -88,7 +86,7 @@ class MergePreview(QWidget): self._keypointCombo.clear() self._keypointCombo.addItems([f"keypoint {i}" for i in range(numkeypoints)]) - def set_dataframe(self, newdf): + def setDataframe(self, newdf): self._dataframe = newdf self._frames = self._dataframe.frame.values self._keypoints = self._dataframe.keypoints.values @@ -115,11 +113,11 @@ class MergePreview(QWidget): self._startLabel.setText(str(self._startSlider.value())) self._stopLabel.setText(str(self._stopSlider.value())) - def set_image(self, img): + def setImage(self, img): self._image = img self.update() - def _draw_detections(self): + def _drawDetections(self): start_frame = self._startSlider.value() stop_frame = self._stopSlider.value() selection = self._keypoints[self._frames[(self._frames >= start_frame) & (self._frames < stop_frame)]] @@ -145,7 +143,7 @@ class MergePreview(QWidget): self._view.setScene(self._scene) self._view.fitInView(self._scene.sceneRect(), aspectRadioMode=Qt.AspectRatioMode.KeepAspectRatio) self._view.show() - self._draw_detections() + self._drawDetections() def fit_image_to_view(self): """Scale the image to fit the QGraphicsView.""" diff --git a/fixtracks/widgets/taskwidget.py b/fixtracks/widgets/taskwidget.py index 6770260..3518ac7 100644 --- a/fixtracks/widgets/taskwidget.py +++ b/fixtracks/widgets/taskwidget.py @@ -79,10 +79,6 @@ class TasksWidget(QWidget): l.addWidget(mergeBtn, 1, 1, 1, 1, Qt.AlignmentFlag.AlignCenter) l.addWidget(tracksBtn, 2, 1, 1, 1, Qt.AlignmentFlag.AlignCenter) - # l.addWidget(folderBtn) - # l.addWidget(self.convertBtn) - # l.addWidget(self.mergeBtn) - # l.addWidget(self.tracksBtn) self.setLayout(l) diff --git a/fixtracks/widgets/taskwidgets.py b/fixtracks/widgets/taskwidgets.py deleted file mode 100644 index b3ec599..0000000 --- a/fixtracks/widgets/taskwidgets.py +++ /dev/null @@ -1,19 +0,0 @@ -import logging - -from PySide6.QtWidgets import QWidget, QGridLayout, QVBoxLayout, QLabel, QPushButton, QFileDialog, QHBoxLayout, QComboBox, QSizePolicy -from PySide6.QtCore import QThreadPool -from PySide6.QtGui import QImage, QPixmap - -from fixtracks.utils.reader import ImageReader - -class FixTracks(QWidget): - def __init__(self, parent=None): - super().__init__(parent) - self._files = None - layout = QVBoxLayout() - layout.addWidget(QLabel("Fix Tracks!")) - self.setLayout(layout) - - @property - def fileList(self, file_list): - self._files = file_list \ No newline at end of file diff --git a/fixtracks/widgets/tracks.py b/fixtracks/widgets/tracks.py new file mode 100644 index 0000000..fcb6e88 --- /dev/null +++ b/fixtracks/widgets/tracks.py @@ -0,0 +1,192 @@ +import logging +import pathlib +import numpy as np + +from PySide6.QtCore import Qt, QThreadPool, Signal +from PySide6.QtGui import QImage, QStandardItemModel, QStandardItem +from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSizePolicy +from PySide6.QtWidgets import QSpinBox, QSpacerItem, QFileDialog, QProgressBar, QTableView, QSplitter + +from fixtracks.utils.reader import PickleLoader +from fixtracks.widgets.detectionview import DetectionView +from fixtracks.widgets.timeline import Timeline + +class FixTracks(QWidget): + back = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + self._files = [] + self._threadpool = QThreadPool() + self._reader = None + self._image = None + self._dataframe = None + self._unassignedmodel = None + self._leftmodel = None + self._rightmodel = None + + self._detectionView = DetectionView() + self._progress_bar = QProgressBar(self) + self._progress_bar.setMaximumHeight(20) + # self._progress_bar.setRange(0, 0) # Set the progress bar to be indeterminate + self._progress_bar.setValue(0) + self._tasklabel = QLabel() + + self._timeline = Timeline() + self._timeline.signals.changed.connect(self.on_windowChanged) + self._windowspinner = QSpinBox() + self._windowspinner.setRange(100, 10000) + self._windowspinner.setSingleStep(100) + self._windowspinner.valueChanged.connect(self.on_windowSizeChanged) + + timelinebox = QHBoxLayout() + timelinebox.addWidget(self._timeline) + timelinebox.addWidget(QLabel("Window")) + timelinebox.addWidget(self._windowspinner) + + self._left_table = QTableView() + assign1 = QPushButton("<<") + assign1.clicked.connect(self.on_assignLeft) + assign2 = QPushButton(">>") + assign2.clicked.connect(self.on_assignRight) + self._unassigned_table = QTableView() + self._right_table = QTableView() + tablebox = QHBoxLayout() + tablebox.addWidget(self._left_table) + tablebox.addWidget(assign1) + tablebox.addWidget(self._unassigned_table) + tablebox.addWidget(assign2) + tablebox.addWidget(self._right_table) + + self._openBtn = QPushButton("Open") + self._openBtn.setEnabled(True) + self._openBtn.clicked.connect(self._on_open) + self._saveBtn = QPushButton("Save") + self._saveBtn.setEnabled(False) + self._saveBtn.clicked.connect(self.on_save) + self._backBtn = QPushButton("Back") + self._backBtn.clicked.connect(self.on_back) + + btnBox = QHBoxLayout() + btnBox.setAlignment(Qt.AlignmentFlag.AlignLeft) + btnBox.addWidget(self._backBtn) + btnBox.addItem(QSpacerItem(100, 10, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)) + btnBox.addWidget(self._tasklabel) + btnBox.addWidget(self._progress_bar) + btnBox.addWidget(self._openBtn) + btnBox.addWidget(self._saveBtn) + + vbox = QVBoxLayout() + vbox.addLayout(timelinebox) + vbox.addLayout(tablebox) + vbox.addLayout(btnBox) + container = QWidget() + container.setLayout(vbox) + + splitter = QSplitter(Qt.Orientation.Horizontal) + splitter.addWidget(self._detectionView) + splitter.addWidget(container) + splitter.setStretchFactor(0, 3) + splitter.setStretchFactor(1, 1) + layout = QHBoxLayout() + layout.addWidget(splitter) + self.setLayout(layout) + + def _on_open(self): + infile = None + imgfile = None + + self._tasklabel.setText( "Select merged image") + file_dialog = QFileDialog(self, "Select merged image") + file_dialog.setFileMode(QFileDialog.ExistingFile) + file_dialog.setNameFilters([ + "Image Files (*.png *.jpg *.jpeg)", + "All Files (*)" + ]) + if file_dialog.exec(): + imgfile = file_dialog.selectedFiles()[0] + if imgfile is not None: + img = QImage(imgfile) + self._detectionView.setImage(img) + self._tasklabel.setText( "Open data") + file_dialog = QFileDialog(self, "Select pickled DataFrame", "", "Pandas DataFrame (*.pkl)") + if file_dialog.exec(): + infile = file_dialog.selectedFiles()[0] + if infile is not None: + self._progress_bar.setRange(0,0) + self._reader = PickleLoader(infile) + self._reader.signals.finished.connect(self._on_dataOpenend) + self._threadpool.start(self._reader) + + + def populateTables(self): + left_trackid = 1 + right_trackid = 2 + start_frame = self._timeline.sliderPosition - self._windowspinner.value() // 2 + stop_frame = self._timeline.sliderPosition + self._windowspinner.value() // 2 + df = self._dataframe[(self._dataframe.frame >= start_frame) & (self._dataframe.frame < stop_frame)] + + assigned_left = df[(df.track == left_trackid)] + assigned_right = df[(df.track == right_trackid)] + unassigned = df[(df.track != left_trackid) & (df.track != right_trackid)] + print(len(assigned_left), len(assigned_right), len(unassigned)) + columns = ["frame", "track id"] + self._unassignedmodel = QStandardItemModel(len(unassigned), 2) + self._unassignedmodel.setHorizontalHeaderLabels(columns) + self._leftmodel = QStandardItemModel(len(assigned_left), 2) + self._leftmodel.setHorizontalHeaderLabels(columns) + self._rightmodel = QStandardItemModel(len(assigned_right), 2) + self._rightmodel.setHorizontalHeaderLabels(columns) + + # Populate the models with data + for i in range(len(unassigned)): + row = unassigned.iloc[i] + if i == 0: print(row) + for j in range(len(columns)): + item = QStandardItem(f"{i, j}") + self._unassignedmodel.setItem(i, j, item) + self._unassigned_table.setModel(self._unassignedmodel) + + for i in range(len(assigned_left)): + row = assigned_left.iloc[i] + for j in range(len(columns)): + item = QStandardItem(f"{i, j}") + self._leftmodel.setItem(i, j, item) + self._left_table.setModel(self._leftmodel) + + for i in range(len(assigned_right)): + row = assigned_right.iloc[i] + for j in range(len(columns)): + item = QStandardItem(f"{i, j}") + self._rightmodel.setItem(i, j, item) + self._right_table.setModel(self._rightmodel) + + + def _on_dataOpenend(self, state): + logging.info("Finished loading data with state %s", state) + self._tasklabel.setText("") + self._progress_bar.setRange(0, 100) + self._progress_bar.setValue(0) + if state and self._reader is not None: + self._dataframe = self._reader.data + self._timeline.setRange(np.max(self._dataframe.frame.values), self._windowspinner.value()) + self.populateTables() + + def on_save(self): + logging.debug("Save fixtracks results") + + def on_back(self): + logging.debug("Back button pressed!") + self.back.emit() + + def on_assignLeft(self): + pass + + def on_assignRight(self): + pass + + def on_windowChanged(self, value): + self.populateTables() + + def on_windowSizeChanged(self, value): + self._timeline.setWindowWidth(value)