Skip to content

Commit

Permalink
feat: Implement Video Manager with Import, Load, and Delete Features
Browse files Browse the repository at this point in the history
- Developed a  using PyQt5 to manage video imports for the project.
- Added support for importing videos recursively from folders, including handling subfolders.
- Integrated functionality to display imported videos in a table with columns for video name, path, load, and delete actions.
- Implemented a check to prevent duplicate videos from being added to the table, using a set to track imported video paths.
- Connected the Load button to emit a signal for loading videos, enabling seamless integration with the .
- Enhanced delete functionality to remove videos from the table and allow re-importing if deleted.
  • Loading branch information
healthonrails committed Dec 10, 2024
1 parent 1337222 commit 7e69ad2
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 80 deletions.
187 changes: 107 additions & 80 deletions annolid/gui/app.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,75 @@
from annolid.gui.widgets.video_manager import VideoManagerWidget
from annolid.gui.workers import FlexibleWorker, LoadFrameThread
from annolid.gui.shape import Shape
from labelme.app import MainWindow
from labelme.utils import newAction
from labelme.widgets import BrightnessContrastDialog
from labelme.widgets import LabelListWidgetItem
from labelme import utils
from annolid.utils.logger import logger
from annolid.utils.files import count_json_files
from labelme.widgets import ToolBar
from annolid.gui.label_file import LabelFileError
from annolid.gui.label_file import LabelFile
from annolid.configs import get_config
from annolid.gui.widgets.canvas import Canvas
from annolid.gui.widgets.text_prompt import AiRectangleWidget
from annolid.annotation import labelme2coco
from annolid.data import videos
from annolid.gui.widgets import ExtractFrameDialog
from annolid.gui.widgets import ConvertCOODialog
from annolid.gui.widgets import TrainModelDialog
from annolid.gui.widgets import Glitter2Dialog
from annolid.gui.widgets import QualityControlDialog
from annolid.gui.widgets import TrackDialog
from annolid.gui.widgets import SystemInfoDialog
from annolid.postprocessing.glitter import tracks2nix
from annolid.postprocessing.quality_control import TracksResults
from annolid.gui.widgets import ProgressingWindow
import webbrowser
import atexit
from annolid.gui.widgets.video_slider import VideoSlider, VideoSliderMark
from annolid.gui.widgets.step_size_widget import StepSizeWidget
from annolid.gui.widgets.downsample_videos_dialog import VideoRescaleWidget
from annolid.gui.widgets.convert_sleap_dialog import ConvertSleapDialog
from annolid.gui.widgets.extract_keypoints_dialog import ExtractShapeKeyPointsDialog
from annolid.gui.widgets.convert_labelme2csv_dialog import LabelmeJsonToCsvDialog
from annolid.postprocessing.quality_control import pred_dict_to_labelme
from annolid.annotation.timestamps import convert_frame_number_to_time
from annolid.segmentation.SAM.edge_sam_bg import VideoProcessor
from annolid.annotation import labelme2csv
from annolid.gui.widgets.advanced_parameters_dialog import AdvancedParametersDialog
from annolid.gui.widgets.place_preference_dialog import TrackingAnalyzerDialog
from annolid.data.videos import get_video_files
from annolid.gui.widgets.caption import CaptionWidget
from labelme.ai import MODELS
from qtpy import QtCore
from qtpy.QtCore import Qt
from qtpy import QtWidgets
from qtpy import QtGui
from labelme import PY2
from labelme import QT5
from PIL import ImageQt
import pandas as pd
import numpy as np
import torch
import codecs
import imgviz
import argparse
from pathlib import Path
import functools
import requests
import subprocess
import re
import csv
import os.path as osp
import time
import html
import shutil
import sys
import os
# Enable CPU fallback for unsupported MPS ops
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
import shutil
import html
import time
import os.path as osp
import csv
import re
import subprocess
import requests
import functools
from pathlib import Path
import argparse
import imgviz
import codecs
import torch
import numpy as np
import pandas as pd
from PIL import ImageQt
from labelme import QT5
from labelme import PY2
from qtpy import QtGui
from qtpy import QtWidgets
from qtpy.QtCore import Qt
from qtpy import QtCore
from labelme.ai import MODELS
from annolid.gui.widgets.caption import CaptionWidget
from annolid.data.videos import get_video_files
from annolid.gui.widgets.place_preference_dialog import TrackingAnalyzerDialog
from annolid.gui.widgets.advanced_parameters_dialog import AdvancedParametersDialog
from annolid.annotation import labelme2csv
from annolid.segmentation.SAM.edge_sam_bg import VideoProcessor
from annolid.annotation.timestamps import convert_frame_number_to_time
from annolid.postprocessing.quality_control import pred_dict_to_labelme
from annolid.gui.widgets.convert_labelme2csv_dialog import LabelmeJsonToCsvDialog
from annolid.gui.widgets.extract_keypoints_dialog import ExtractShapeKeyPointsDialog
from annolid.gui.widgets.convert_sleap_dialog import ConvertSleapDialog
from annolid.gui.widgets.downsample_videos_dialog import VideoRescaleWidget
from annolid.gui.widgets.step_size_widget import StepSizeWidget
from annolid.gui.widgets.video_slider import VideoSlider, VideoSliderMark
import atexit
import webbrowser
from annolid.gui.widgets import ProgressingWindow
from annolid.postprocessing.quality_control import TracksResults
from annolid.postprocessing.glitter import tracks2nix
from annolid.gui.widgets import SystemInfoDialog
from annolid.gui.widgets import TrackDialog
from annolid.gui.widgets import QualityControlDialog
from annolid.gui.widgets import Glitter2Dialog
from annolid.gui.widgets import TrainModelDialog
from annolid.gui.widgets import ConvertCOODialog
from annolid.gui.widgets import ExtractFrameDialog
from annolid.data import videos
from annolid.annotation import labelme2coco
from annolid.gui.widgets.text_prompt import AiRectangleWidget
from annolid.gui.widgets.canvas import Canvas
from annolid.configs import get_config
from annolid.gui.label_file import LabelFile
from annolid.gui.label_file import LabelFileError
from labelme.widgets import ToolBar
from annolid.utils.files import count_json_files
from annolid.utils.logger import logger
from labelme import utils
from labelme.widgets import LabelListWidgetItem
from labelme.widgets import BrightnessContrastDialog
from labelme.utils import newAction
from labelme.app import MainWindow
from annolid.gui.shape import Shape
from annolid.gui.workers import FlexibleWorker, LoadFrameThread


__appname__ = 'Annolid'
__version__ = "1.2.1"
Expand Down Expand Up @@ -129,6 +129,21 @@ def __init__(self,
self.label_dock.setVisible(True)
self.shape_dock.setVisible(True)
self.file_dock.setVisible(True)

# Create the Video Manager Widget
self.video_manager_widget = VideoManagerWidget()
self.video_manager_widget.video_selected.connect(self._load_video)

# Create the Dock Widget
self.video_dock = QtWidgets.QDockWidget("Video List", self)
self.video_dock.setWidget(self.video_manager_widget)
self.video_dock.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetClosable |
QtWidgets.QDockWidget.DockWidgetFloatable)

# Add the Dock Widget to the Main Window
self.addDockWidget(Qt.RightDockWidgetArea, self.video_dock)

self.here = Path(__file__).resolve().parent
action = functools.partial(newAction, self)
self._df = None
Expand Down Expand Up @@ -2075,7 +2090,16 @@ def _load_labels(self, labels_csv_file):
self._df = pd.read_csv(labels_csv_file)
self._df.rename(columns={'Unnamed: 0': 'frame_number'}, inplace=True)

def openVideo(self, _value=False):
def _load_video(self, video_path):
"""Open a video for annotation frame by frame."""
if not video_path:
return
self.openVideo(from_video_list=True, video_path=video_path)

def openVideo(self, _value=False,
from_video_list=False,
video_path=None,
):
"""open a video for annotaiton frame by frame
Args:
Expand All @@ -2097,17 +2121,20 @@ def openVideo(self, _value=False):
elif choice == QtWidgets.QMessageBox.Cancel:
return # Cancel operation

video_path = Path(self.filename).parent if self.filename else "."
formats = ["*.*"]
filters = self.tr(f"Video files {formats[0]}")
video_filename = QtWidgets.QFileDialog.getOpenFileName(
self,
self.tr(f"{__appname__} - Choose Video"),
str(video_path),
filters,
)
if QT5:
video_filename, _ = video_filename
if not from_video_list:
video_path = Path(self.filename).parent if self.filename else "."
formats = ["*.*"]
filters = self.tr(f"Video files {formats[0]}")
video_filename = QtWidgets.QFileDialog.getOpenFileName(
self,
self.tr(f"{__appname__} - Choose Video"),
str(video_path),
filters,
)
if QT5:
video_filename, _ = video_filename
else:
video_filename = video_path

video_filename = str(video_filename)
self.stepSizeWidget.setEnabled(True)
Expand Down
92 changes: 92 additions & 0 deletions annolid/gui/widgets/video_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import os
from qtpy.QtCore import Signal
from qtpy.QtWidgets import (
QWidget, QVBoxLayout, QPushButton, QTableWidget, QTableWidgetItem,
QFileDialog, QMessageBox, QAbstractItemView
)


class VideoManagerWidget(QWidget):
video_selected = Signal(str) # Signal to send the selected video path

def __init__(self, parent=None):
super().__init__(parent)

# Layouts
self.layout = QVBoxLayout(self)

# Set to track imported videos
self.imported_videos = set()

# Import Button
self.import_button = QPushButton("Import Videos")
self.import_button.clicked.connect(self.import_videos)
self.layout.addWidget(self.import_button)

# Video Table
# Rows: 0, Columns: 4 (Name, Path, Load, Delete)
self.video_table = QTableWidget(0, 4)
self.video_table.setHorizontalHeaderLabels(
["Name", "Path", "Load", "Delete"])
self.video_table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.layout.addWidget(self.video_table)

def import_videos(self):
# Open folder dialog
folder_path = QFileDialog.getExistingDirectory(
self, "Select Video Folder")
if not folder_path:
return

# Get video files (recursively)
video_extensions = {'.mp4', '.avi', '.mov', '.mkv',
'.mpg'} # Add more extensions if needed
video_files = []
for root, _, files in os.walk(folder_path):
for file in files:
if os.path.splitext(file)[1].lower() in video_extensions:
video_files.append(os.path.join(root, file))

# Add videos to the table
for video in video_files:
# Check if video is already added
if video not in self.imported_videos:
self.add_video_to_table(video)

def add_video_to_table(self, video_path):
# Add the video to the imported videos set
self.imported_videos.add(video_path)
# Get video name
video_name = os.path.basename(video_path)

# Create a new row
row_position = self.video_table.rowCount()
self.video_table.insertRow(row_position)

# Add Name and Path
self.video_table.setItem(row_position, 0, QTableWidgetItem(video_name))
self.video_table.setItem(row_position, 1, QTableWidgetItem(video_path))

# Add Load Button
load_button = QPushButton("Load")
load_button.clicked.connect(
lambda: self.video_selected.emit(video_path))
self.video_table.setCellWidget(row_position, 2, load_button)

# Add Delete Button
delete_button = QPushButton("Delete")
delete_button.clicked.connect(
lambda: self.delete_video(row_position, video_path))
self.video_table.setCellWidget(row_position, 3, delete_button)

def delete_video(self, row, video_path):
# Confirm deletion
confirmation = QMessageBox.question(
self, "Delete Video", "Are you sure you want to delete this video from the list?",
QMessageBox.Yes | QMessageBox.No
)
if confirmation == QMessageBox.Yes:
# Remove from the imported videos set
self.imported_videos.discard(video_path)

self.video_table.removeRow(row)

0 comments on commit 7e69ad2

Please sign in to comment.