Initial commit
This commit is contained in:
commit
e03409fcd4
10 changed files with 472 additions and 0 deletions
41
README.md
Executable file
41
README.md
Executable file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Real Video Labeling Tool Py
|
||||||
|
Python rewrite of [Real-Video-Annotation-Tool]
|
||||||
|
|
||||||
|
## 1. Installation
|
||||||
|
Here are two options to setup the project.
|
||||||
|
|
||||||
|
### 1.1.1 Setup with Conda
|
||||||
|
1. If [conda] is not yet installed, follow the [installation guide] for your operating system.
|
||||||
|
> Note: [miniconda] and especially [mamba] are faster and lighter than Anaconda
|
||||||
|
2. From the projects root directory create the environment with:
|
||||||
|
```
|
||||||
|
conda env create -f environment.yml
|
||||||
|
```
|
||||||
|
3. Activate the new environment with:
|
||||||
|
```
|
||||||
|
conda activate labeling_tool
|
||||||
|
```
|
||||||
|
> Note: The environment should always be activated when working on this project
|
||||||
|
4. To update the environment after changes in environment.yml run:
|
||||||
|
```conda env update --name labeling_tool --file environment.yml --prune```
|
||||||
|
|
||||||
|
#### Troubleshoot
|
||||||
|
- Make sure to restart your terminal after installing conda. Or run ```source ~/.bashrc```
|
||||||
|
- Run ```conda config --set auto_activate_base false``` to deactivate automatic conda activation on startup
|
||||||
|
- If ```conda activate``` fails, try ```conda init``` and then ```conda activate``` again
|
||||||
|
|
||||||
|
### 1.1.2 Install dependencies with pip
|
||||||
|
You can also install dependencies with pip:
|
||||||
|
```pip install -r requirements.txt```
|
||||||
|
|
||||||
|
### 1.2 Run
|
||||||
|
Run program with ```python3 src/main.py``` or from within IDE.
|
||||||
|
Images and Labels are stored in /Storage
|
||||||
|
|
||||||
|
|
||||||
|
## Note
|
||||||
|
[Real-Video-Annotation-Tool]: https://sam-dev.cs.hm.edu/SAM-DEV/Sampler_CNN_Training
|
||||||
|
[conda]: https://docs.conda.io/
|
||||||
|
[installation guide]: https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html
|
||||||
|
[miniconda]: https://docs.conda.io/en/latest/miniconda.html
|
||||||
|
[mamba]: https://mamba.readthedocs.io/en/latest/installation.html#installation
|
||||||
0
Storage/.gitkeep
Normal file
0
Storage/.gitkeep
Normal file
15
classes.txt
Normal file
15
classes.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
person
|
||||||
|
child
|
||||||
|
car
|
||||||
|
emergency
|
||||||
|
trafficlight
|
||||||
|
trafficlight_green
|
||||||
|
trafficlight_yellow
|
||||||
|
trafficlight_red
|
||||||
|
pit_in
|
||||||
|
pit_out
|
||||||
|
park_parallel
|
||||||
|
park_cross
|
||||||
|
overtaking_prohibited
|
||||||
|
overtaking_permitted
|
||||||
|
traffic_sign
|
||||||
16
environment.yml
Normal file
16
environment.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
name: labeling_tool
|
||||||
|
|
||||||
|
channels:
|
||||||
|
- conda-forge
|
||||||
|
- nodefaults
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- python==3.9
|
||||||
|
- pip
|
||||||
|
- numpy==1.23.5
|
||||||
|
- tk==8.6.12
|
||||||
|
- pip:
|
||||||
|
- ttkbootstrap==1.10.0
|
||||||
|
- opencv-contrib-python-headless==4.6.0.66
|
||||||
|
- pillow==9.3.0
|
||||||
|
- natsort==8.2.0
|
||||||
187
src/boundingBox.py
Normal file
187
src/boundingBox.py
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
import cv2
|
||||||
|
import random
|
||||||
|
import ttkbootstrap as ttk
|
||||||
|
from tkinter import StringVar, Frame, Label
|
||||||
|
|
||||||
|
|
||||||
|
def get_color() -> str:
|
||||||
|
de = ("%02x" % random.randint(0, 255))
|
||||||
|
re = ("%02x" % random.randint(0, 255))
|
||||||
|
we = ("%02x" % random.randint(0, 255))
|
||||||
|
color = "#" + de + re + we
|
||||||
|
return color
|
||||||
|
|
||||||
|
|
||||||
|
class BoundingBox:
|
||||||
|
bboxes = []
|
||||||
|
current = None
|
||||||
|
counter = 0
|
||||||
|
current_index = 0
|
||||||
|
classes = [(i.strip()) for i in open('classes.txt', 'r').readlines()]
|
||||||
|
classes = dict(zip(classes, range(0, len(classes) + 1)))
|
||||||
|
tracker = cv2.TrackerCSRT_create()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_label(cls, canvas, frm_buttons):
|
||||||
|
BoundingBox(canvas, frm_buttons)
|
||||||
|
cls.current_index = len(cls.bboxes)-1
|
||||||
|
cls.current = cls.bboxes[cls.current_index]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def switch_box(cls, _):
|
||||||
|
try:
|
||||||
|
cls.current_index = (cls.current_index + 1) % len(cls.bboxes)
|
||||||
|
cls.current = cls.bboxes[cls.current_index]
|
||||||
|
cls.current.__set_current()
|
||||||
|
except ZeroDivisionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def move_current(cls, event):
|
||||||
|
cls.current.move(event)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def motion_current(cls, event):
|
||||||
|
cls.current.motion(event)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def track_boxes(cls, images):
|
||||||
|
tracker = cv2.TrackerCSRT_create()
|
||||||
|
for box in cls.bboxes:
|
||||||
|
bbox = (box.start_x, box.start_y, box.x - box.start_x, box.y - box.start_y)
|
||||||
|
tracker.init(images.previous_image, bbox)
|
||||||
|
|
||||||
|
ok, bbox = tracker.update(images.images[images.current_index])
|
||||||
|
|
||||||
|
box.start_x = bbox[0]
|
||||||
|
box.start_y = bbox[1]
|
||||||
|
box.x = box.start_x + bbox[2]
|
||||||
|
box.y = box.start_y + bbox[3]
|
||||||
|
box.update_coords()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def write_labels(cls, file):
|
||||||
|
filename = str(file)
|
||||||
|
filename = filename.replace(".jpg", ".txt")
|
||||||
|
with open(filename, "w") as file:
|
||||||
|
for box in cls.bboxes:
|
||||||
|
width = box.x - box.start_x
|
||||||
|
height = box.y - box.start_y
|
||||||
|
box_properties = [cls.classes[box.lbl_class], box.start_x, box.start_y, width, height]
|
||||||
|
file.writelines(", ".join(map(str, box_properties)))
|
||||||
|
file.write("\n")
|
||||||
|
|
||||||
|
def __init__(self, canvas, frm_buttons):
|
||||||
|
self.canvas = canvas
|
||||||
|
self.frm_buttons = frm_buttons
|
||||||
|
|
||||||
|
BoundingBox.counter += 1
|
||||||
|
|
||||||
|
self.lbl_class: str = next(iter(BoundingBox.classes))
|
||||||
|
self.color: str = get_color()
|
||||||
|
|
||||||
|
# Coordinates
|
||||||
|
self.start_x = 0
|
||||||
|
self.start_y = 0
|
||||||
|
self.x = 50
|
||||||
|
self.y = 50
|
||||||
|
|
||||||
|
# Draw initial Rectangle with Label text
|
||||||
|
self.rect = self.canvas.create_rectangle(self.start_x, self.start_y, self.x, self.y,
|
||||||
|
outline=self.color, width=5)
|
||||||
|
self.text = self.canvas.create_text(self.start_x, self.start_y - 20,
|
||||||
|
text=f"{BoundingBox.counter}-{self.lbl_class}",
|
||||||
|
fill=self.color, font=('Arial', 30), anchor='w')
|
||||||
|
|
||||||
|
# Create Box Selection Frame with Buttons (Select, Remove etc.) for Bounding Box
|
||||||
|
self.lbl_frame = Frame(self.frm_buttons, bg=self.color, width=200, height=80)
|
||||||
|
self.lbl_text = Label(self.lbl_frame, text=f"{BoundingBox.counter}-{self.lbl_class}", font=("Arial", 12))
|
||||||
|
self.lbl_frame.bind("<Button-1>", self.__set_current_click)
|
||||||
|
self.__setup_box_buttons()
|
||||||
|
|
||||||
|
self.__set_current()
|
||||||
|
BoundingBox.bboxes.append(self)
|
||||||
|
|
||||||
|
def move(self, event, motion=False):
|
||||||
|
if motion:
|
||||||
|
self.x, self.y = (event.x, event.y)
|
||||||
|
elif event.num == 1:
|
||||||
|
self.start_x = event.x
|
||||||
|
self.start_y = event.y
|
||||||
|
return
|
||||||
|
elif event.keysym == "a":
|
||||||
|
self.start_x -= 1 # left
|
||||||
|
elif event.keysym == "s":
|
||||||
|
self.start_y += 1 # down
|
||||||
|
elif event.keysym == "w":
|
||||||
|
self.start_y -= 1 # up
|
||||||
|
elif event.keysym == "d":
|
||||||
|
self.start_x += 1 # right
|
||||||
|
elif event.keysym == "j":
|
||||||
|
self.x -= 1 # left
|
||||||
|
elif event.keysym == "k":
|
||||||
|
self.y += 1 # down
|
||||||
|
elif event.keysym == "i":
|
||||||
|
self.y -= 1 # up
|
||||||
|
elif event.keysym == "l":
|
||||||
|
self.x += 1 # right
|
||||||
|
self.update_coords()
|
||||||
|
|
||||||
|
def motion(self, event):
|
||||||
|
self.move(event, motion=True)
|
||||||
|
|
||||||
|
def update_coords(self):
|
||||||
|
self.canvas.coords(self.rect, self.start_x, self.start_y, self.x, self.y)
|
||||||
|
self.canvas.coords(self.text, self.start_x, self.start_y-20)
|
||||||
|
|
||||||
|
def __setup_box_buttons(self):
|
||||||
|
self.lbl_frame.grid(pady=0, padx=2, sticky='ew')
|
||||||
|
self.lbl_frame.grid_propagate(True)
|
||||||
|
|
||||||
|
# Bounding Box Title as number of box and box class
|
||||||
|
self.lbl_text.grid(row=0, column=0, columnspan=3, sticky='w')
|
||||||
|
|
||||||
|
# Class Selection Dropdown
|
||||||
|
variable = StringVar(self.frm_buttons, self.lbl_class)
|
||||||
|
lbl_class_om = ttk.OptionMenu(self.lbl_frame, variable, self.lbl_class, *BoundingBox.classes.keys(),
|
||||||
|
command=self.change_class)
|
||||||
|
lbl_class_om.grid(row=1, column=0, pady=10, columnspan=3)
|
||||||
|
|
||||||
|
# Buttons (Select, New Color and Remove)
|
||||||
|
select_btn = ttk.Button(self.lbl_frame, text='Select', command=self.__set_current)
|
||||||
|
select_btn.grid(row=2, column=0, pady=10)
|
||||||
|
|
||||||
|
new_color = ttk.Button(self.lbl_frame, text='New Color', command=self.new_color)
|
||||||
|
new_color.grid(row=2, column=1, pady=10)
|
||||||
|
|
||||||
|
rm_btn = ttk.Button(self.lbl_frame, text='Remove', command=self.remove)
|
||||||
|
rm_btn.grid(row=2, column=2, pady=10)
|
||||||
|
|
||||||
|
self.new_color()
|
||||||
|
|
||||||
|
def change_class(self, event):
|
||||||
|
self.lbl_class = event
|
||||||
|
self.lbl_text.configure(text=f"{BoundingBox.counter}-{self.lbl_class}") # TODO fix counter when changing class
|
||||||
|
self.canvas.itemconfig(self.text, text=f"{BoundingBox.counter}-{self.lbl_class}")
|
||||||
|
|
||||||
|
def __set_current_click(self, _):
|
||||||
|
self.__set_current()
|
||||||
|
|
||||||
|
def __set_current(self):
|
||||||
|
BoundingBox.current = self
|
||||||
|
for box in BoundingBox.bboxes:
|
||||||
|
box.lbl_frame.configure(highlightthickness=0)
|
||||||
|
box.canvas.itemconfigure(box.rect, width=3)
|
||||||
|
self.canvas.itemconfigure(self.rect, width=5)
|
||||||
|
self.lbl_frame.configure(highlightbackground="white", highlightcolor="white", highlightthickness=6)
|
||||||
|
|
||||||
|
def new_color(self):
|
||||||
|
self.color = get_color()
|
||||||
|
self.canvas.itemconfig(self.rect, outline=self.color)
|
||||||
|
self.canvas.itemconfig(self.text, fill=self.color)
|
||||||
|
self.lbl_frame.configure(background=self.color, highlightbackground="white", highlightcolor="white")
|
||||||
|
|
||||||
|
def remove(self):
|
||||||
|
self.lbl_frame.destroy()
|
||||||
|
self.canvas.delete(self.rect, self.text)
|
||||||
|
BoundingBox.bboxes = [box for box in BoundingBox.bboxes if box != self]
|
||||||
46
src/imageHandler.py
Normal file
46
src/imageHandler.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import cv2
|
||||||
|
import pathlib
|
||||||
|
from tkinter.filedialog import askopenfilename
|
||||||
|
from natsort import natsorted
|
||||||
|
|
||||||
|
IMAGE_PATH = "Storage/" # TODO: Windows??
|
||||||
|
|
||||||
|
|
||||||
|
class ImageHandler:
|
||||||
|
def __init__(self):
|
||||||
|
self.filepath = askopenfilename(filetypes=[("Text Files", "*.mp4"), ("All Files", "*.*")])
|
||||||
|
self.image_path = IMAGE_PATH
|
||||||
|
self.video = cv2.VideoCapture(self.filepath)
|
||||||
|
|
||||||
|
# Read Video with opencv
|
||||||
|
success, self.current_cv_read = self.video.read()
|
||||||
|
self.current_index = 0
|
||||||
|
self.images = [self.current_cv_read]
|
||||||
|
self.previous_image = self.images[self.current_index]
|
||||||
|
|
||||||
|
# Read first frame
|
||||||
|
self.write_img()
|
||||||
|
self.current_img = pathlib.Path(self.get_file_list()[self.current_index])
|
||||||
|
|
||||||
|
def write_img(self):
|
||||||
|
cv2.imwrite(f"{self.image_path}frame%d.jpg" % self.current_index, self.current_cv_read)
|
||||||
|
|
||||||
|
def get_file_list(self):
|
||||||
|
return natsorted(list(pathlib.Path(self.image_path).glob("*.jpg")))
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
self.current_index += 1
|
||||||
|
# If we want to read a new image
|
||||||
|
if self.current_index == len(self.images):
|
||||||
|
success, self.current_cv_read = self.video.read()
|
||||||
|
self.write_img()
|
||||||
|
self.images.append(self.current_cv_read)
|
||||||
|
|
||||||
|
self.current_img = pathlib.Path(self.get_file_list()[self.current_index])
|
||||||
|
self.previous_image = self.images[self.current_index - 1]
|
||||||
|
|
||||||
|
def previous(self):
|
||||||
|
if self.current_index > 0:
|
||||||
|
self.current_index -= 1
|
||||||
|
self.previous_image = self.images[self.current_index-1]
|
||||||
|
self.current_img = pathlib.Path(self.get_file_list()[self.current_index])
|
||||||
57
src/imageViewer.py
Normal file
57
src/imageViewer.py
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import ttkbootstrap as ttk
|
||||||
|
from PIL import ImageTk, Image
|
||||||
|
import cv2
|
||||||
|
from boundingBox import BoundingBox
|
||||||
|
from imageHandler import ImageHandler
|
||||||
|
|
||||||
|
|
||||||
|
class ImageViewer(ttk.Canvas):
|
||||||
|
def __init__(self, window, frame):
|
||||||
|
# Initialize and create Tkinter Canvas
|
||||||
|
super().__init__(frame, width=window.winfo_width(), height=window.winfo_height(), cursor="cross")
|
||||||
|
self.pack(side="top", expand=True)
|
||||||
|
|
||||||
|
# Create boxes and Image Handler
|
||||||
|
self.image = ImageHandler()
|
||||||
|
|
||||||
|
self.displayed_img = self.__get_current_photo_image()
|
||||||
|
self.image_container = self.create_image(0, 0, anchor="nw", image=self.displayed_img)
|
||||||
|
self.display_img()
|
||||||
|
|
||||||
|
# Keyboard Bindings
|
||||||
|
self.focus_set()
|
||||||
|
window.bind("<Right>", self.forward)
|
||||||
|
window.bind("<Left>", self.backward)
|
||||||
|
self.bind("<Shift_L>", BoundingBox.switch_box)
|
||||||
|
self.bind("<Button-1>", BoundingBox.move_current)
|
||||||
|
self.bind("<B1-Motion>", BoundingBox.motion_current)
|
||||||
|
self.bind("a", BoundingBox.move_current)
|
||||||
|
self.bind("s", BoundingBox.move_current)
|
||||||
|
self.bind("w", BoundingBox.move_current)
|
||||||
|
self.bind("d", BoundingBox.move_current)
|
||||||
|
self.bind("j", BoundingBox.move_current)
|
||||||
|
self.bind("k", BoundingBox.move_current)
|
||||||
|
self.bind("i", BoundingBox.move_current)
|
||||||
|
self.bind("l", BoundingBox.move_current)
|
||||||
|
|
||||||
|
def __get_current_photo_image(self) -> ImageTk.PhotoImage:
|
||||||
|
rgb_image = cv2.cvtColor(self.image.current_cv_read, cv2.COLOR_BGR2RGB)
|
||||||
|
img = Image.fromarray(rgb_image)
|
||||||
|
return ImageTk.PhotoImage(img)
|
||||||
|
|
||||||
|
def display_img(self):
|
||||||
|
# Opencv reads frames as bgr image, we need to convert it into rgb to display it correctly
|
||||||
|
rgb_image = cv2.cvtColor(self.image.images[self.image.current_index], cv2.COLOR_BGR2RGB)
|
||||||
|
img = Image.fromarray(rgb_image)
|
||||||
|
self.displayed_img = ImageTk.PhotoImage(img)
|
||||||
|
self.itemconfig(self.image_container, image=self.displayed_img)
|
||||||
|
|
||||||
|
def forward(self, _):
|
||||||
|
BoundingBox.write_labels(file=self.image.current_img)
|
||||||
|
self.image.next()
|
||||||
|
self.display_img()
|
||||||
|
BoundingBox.track_boxes(self.image)
|
||||||
|
|
||||||
|
def backward(self, _):
|
||||||
|
self.image.previous()
|
||||||
|
self.display_img()
|
||||||
5
src/main.py
Executable file
5
src/main.py
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
from window import Window
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
window = Window()
|
||||||
|
window.mainloop()
|
||||||
49
src/settings.py
Normal file
49
src/settings.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import cv2
|
||||||
|
import ttkbootstrap as ttk
|
||||||
|
from tkinter import Toplevel, Frame, Label, IntVar
|
||||||
|
from boundingBox import BoundingBox
|
||||||
|
|
||||||
|
"""
|
||||||
|
WIP
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(Toplevel):
|
||||||
|
def __init__(self, master=None):
|
||||||
|
Toplevel.__init__(self, master)
|
||||||
|
self.wm_title("Settings")
|
||||||
|
self.rowconfigure(0, minsize=480, weight=1)
|
||||||
|
self.columnconfigure(0, minsize=640, weight=1)
|
||||||
|
self.grab_set()
|
||||||
|
self.frame = Frame(self, padx=80, pady=40)
|
||||||
|
self.frame.pack(side='top')
|
||||||
|
|
||||||
|
# Tracker type options
|
||||||
|
tracker_type_txt = Label(self.frame, text="Tracker Type (WIP)", font=('Arial', 14), pady=20)
|
||||||
|
tracker_type_txt.pack()
|
||||||
|
self.trackers = {"CSRT": 1,
|
||||||
|
"GOTURN": 2, # TODO
|
||||||
|
"KCF": 3} # TODO
|
||||||
|
|
||||||
|
self.v2 = IntVar(self.frame, 1)
|
||||||
|
self.v2.trace_add('write', self.set_tracker)
|
||||||
|
for (text, value) in self.trackers.items():
|
||||||
|
ttk.Radiobutton(self.frame, text=text, variable=self.v2, value=value).pack(side='top', anchor='w')
|
||||||
|
|
||||||
|
# Label Revision Options
|
||||||
|
label_revision_txt = Label(self.frame, text="Label Revision (WIP)", font=('Arial', 14), pady=20)
|
||||||
|
label_revision_txt.pack()
|
||||||
|
revision_types = {"Overwrite Files (Tracker active)": 1,
|
||||||
|
"Add (Tracker only for new Boxes)": 2, # TODO
|
||||||
|
"Adjust (No Tracker)": 3} # TODO
|
||||||
|
v = IntVar(self.frame, 1)
|
||||||
|
for (text, value) in revision_types.items():
|
||||||
|
ttk.Radiobutton(self.frame, text=text, variable=v, value=value).pack(side='top', anchor='w')
|
||||||
|
|
||||||
|
def set_tracker(self, _):
|
||||||
|
cv_trackers = {1: cv2.TrackerCSRT_create(),
|
||||||
|
2: cv2.TrackerGOTURN_create(),
|
||||||
|
3: cv2.TrackerKCF_create()}
|
||||||
|
|
||||||
|
BoundingBox.tracker = cv_trackers[self.v2.get()]
|
||||||
|
print(f"Tracker type changed to {BoundingBox.tracker}")
|
||||||
56
src/window.py
Normal file
56
src/window.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import Menu
|
||||||
|
import ttkbootstrap as ttk
|
||||||
|
from imageViewer import ImageViewer
|
||||||
|
from settings import Settings
|
||||||
|
from boundingBox import BoundingBox
|
||||||
|
|
||||||
|
IMAGE_SIZE_X = 960
|
||||||
|
IMAGE_SIZE_Y = 1280
|
||||||
|
|
||||||
|
|
||||||
|
class Window(ttk.Window):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(themename="superhero")
|
||||||
|
|
||||||
|
# Set window properties
|
||||||
|
self.wm_title("Labeling Tool")
|
||||||
|
self.rowconfigure(0, minsize=IMAGE_SIZE_X, weight=1)
|
||||||
|
self.columnconfigure(0, minsize=IMAGE_SIZE_Y, weight=1)
|
||||||
|
|
||||||
|
# Set Menu Bar for Settings and opening Video file
|
||||||
|
self.__menu_bar()
|
||||||
|
|
||||||
|
# Sidebar (Frame) for Buttons and BoundingBox Selection
|
||||||
|
frm_buttons = tk.Frame(self, relief=tk.RAISED, bd=1)
|
||||||
|
frm_buttons.grid(row=0, column=2, sticky="ns")
|
||||||
|
|
||||||
|
# Image Frame to display Image as Canvas
|
||||||
|
frm_image = tk.Frame(self, relief=tk.RAISED)
|
||||||
|
frm_image.grid(row=0, column=0, sticky="ew")
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
# Load Canvas into image Frame to display Video frame and draw BBoxes
|
||||||
|
image_view = ImageViewer(self, frm_image)
|
||||||
|
|
||||||
|
# Button for adding Bounding Boxes
|
||||||
|
btn_add_class = tk.Button(frm_buttons, text="Add Class",
|
||||||
|
command=lambda: BoundingBox.new_label(image_view, frm_buttons))
|
||||||
|
btn_add_class.grid(row=0, column=0, sticky="nwe", padx=5, pady=5)
|
||||||
|
|
||||||
|
def __menu_bar(self):
|
||||||
|
menu = Menu()
|
||||||
|
self.config(menu=menu)
|
||||||
|
|
||||||
|
# File Opener Menu
|
||||||
|
file_menu = Menu(menu)
|
||||||
|
menu.add_cascade(label='File', menu=file_menu)
|
||||||
|
# file_menu.add_command(label='Open...', command=Fileloader.open_video)
|
||||||
|
|
||||||
|
# Settings Menu
|
||||||
|
settings = Menu(menu)
|
||||||
|
menu.add_cascade(label='Settings', menu=settings)
|
||||||
|
settings.add_command(label='Settings', command=self.__open_settings)
|
||||||
|
|
||||||
|
def __open_settings(self):
|
||||||
|
Settings(self)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue