Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

task/1682-create-gui-to-define-sections #97

Merged
merged 19 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 23 additions & 49 deletions OTAnalytics/plugin_ui/application.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import tkinter
from abc import abstractmethod
from pathlib import Path

import customtkinter
from customtkinter import CTk, CTkButton
from customtkinter import CTk

from OTAnalytics.application.datastore import Datastore
from OTAnalytics.domain.section import Coordinate, LineSection, Section
from OTAnalytics.domain.track import CalculateTrackClassificationByMaxConfidence
from OTAnalytics.plugin_parser.otvision_parser import (
OtEventListParser,
OtsectionParser,
OttrkParser,
OttrkVideoParser,
)
from OTAnalytics.plugin_ui.constants import PADX, STICKY
from OTAnalytics.plugin_ui.frame_canvas import FrameCanvas, TrackImage
from OTAnalytics.plugin_ui.frame_sections import FrameSections
from OTAnalytics.plugin_ui.frame_tracks import FrameTracks


class OTAnalyticsApplication:
Expand Down Expand Up @@ -48,63 +50,35 @@ def __init__(self, datastore: Datastore, app: CTk = CTk()) -> None:
super().__init__(datastore)
self._app: CTk = app

def _load_tracks_in_file(self) -> None:
track_file = Path("") # TODO read from file chooser
self._datastore.load_track_file(file=track_file)

def _load_sections_in_file(self) -> None:
section_file = Path("") # TODO read from file chooser
self._datastore.load_section_file(file=section_file)

def _save_sections_to_file(self) -> None:
section_file = Path("") # TODO read from file choser
self._datastore.save_section_file(file=section_file)

def start_internal(self) -> None:
self._show_gui()

def _show_gui(self) -> None:
customtkinter.set_appearance_mode("System")
customtkinter.set_default_color_theme("blue")

self._app.geometry("800x600")
customtkinter.set_default_color_theme("green")

self._add_track_loader()
self._add_section_loader()
self._app.title("OTAnalytics")

self._app.mainloop()

def _add_track_loader(self) -> None:
button = CTkButton(
master=self._app,
text="Read tracks",
command=self._load_tracks_in_file,
)
button.place(relx=0.25, rely=0.5, anchor=tkinter.CENTER)

def _add_section_loader(self) -> None:
button = CTkButton(
master=self._app,
text="Read sections",
command=self._load_sections_in_file,
self._get_widgets()
self._place_widgets()
image = TrackImage(
Path(r"tests/data/Testvideo_Cars-Cyclist_FR20_2020-01-01_00-00-00.mp4")
)
button.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
self.frame_canvas.add_image(image)
self._app.mainloop()

def _add_section_button(self) -> None:
button = CTkButton(
master=self._app,
text="Add sections",
command=self._add_section,
)
button.place(relx=0.75, rely=0.5, anchor=tkinter.CENTER)
def _get_widgets(self) -> None:
self.frame_canvas = FrameCanvas(master=self._app)
self.frame_tracks = FrameTracks(master=self._app, datastore=self._datastore)
self.frame_sections = FrameSections(master=self._app)

def _add_section(self) -> None:
section: Section = LineSection(
id="north",
start=Coordinate(0, 1),
end=Coordinate(2, 3),
def _place_widgets(self) -> None:
PADY = 10
self.frame_canvas.grid(
row=0, column=0, rowspan=2, padx=PADX, pady=PADY, sticky=STICKY
)
self._datastore.add_section(section)
self.frame_tracks.grid(row=0, column=1, padx=PADX, pady=PADY, sticky=STICKY)
self.frame_sections.grid(row=1, column=1, padx=PADX, pady=PADY, sticky=STICKY)


class ApplicationStarter:
Expand Down
3 changes: 3 additions & 0 deletions OTAnalytics/plugin_ui/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PADX = 10
PADY = 5
STICKY = "NESW"
75 changes: 75 additions & 0 deletions OTAnalytics/plugin_ui/frame_canvas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import customtkinter
from customtkinter import CTkCanvas, CTkFrame
from moviepy.editor import VideoFileClip
from PIL import Image, ImageTk

from OTAnalytics.plugin_ui.constants import PADX, STICKY


@dataclass
class TrackImage:
path: Path

def load_image(self) -> Any:
video = VideoFileClip(str(self.path))
return video.get_frame(0)

def width(self) -> int:
return self.pillow_image.width

def height(self) -> int:
return self.pillow_image.height

def convert_image(self) -> None:
self.pillow_image = Image.fromarray(self.load_image())

def create_photo(self) -> ImageTk.PhotoImage:
self.convert_image()
self.pillow_photo_image = ImageTk.PhotoImage(image=self.pillow_image)
return self.pillow_photo_image


class FrameCanvas(CTkFrame):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self._get_widgets()
self._place_widgets()

def _get_widgets(self) -> None:
self.canvas_background = CanvasBackground(master=self)

def _place_widgets(self) -> None:
PADY = 10
self.canvas_background.grid(
row=0, column=0, padx=PADX, pady=PADY, sticky=STICKY
)

def add_image(self, image: TrackImage) -> None:
self.canvas_background.add_image(image)
PADX = 10
PADY = 5
STICKY = "NESW"
self.canvas_background.grid(
row=0, column=0, padx=PADX, pady=PADY, sticky=STICKY
)


class CanvasBackground(CTkCanvas):
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)

def add_image(self, image: TrackImage) -> None:
self.create_image(0, 0, image=image.create_photo(), anchor=customtkinter.NW)
self.config(width=image.width(), height=image.height())

def show_rectangle(self) -> None:
self.create_rectangle(10, 10, 70, 70)

def on_click(self, event: Any) -> None:
x = event.x
y = event.y
print(f"Canvas clicked at x={x} and y={y}")
201 changes: 201 additions & 0 deletions OTAnalytics/plugin_ui/frame_sections.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
from tkinter import Listbox
from tkinter.filedialog import askopenfilename, asksaveasfilename
from tkinter.ttk import Treeview
from typing import Any

from customtkinter import CTkButton, CTkFrame, CTkLabel

from OTAnalytics.plugin_ui.constants import PADX, PADY, STICKY
from OTAnalytics.plugin_ui.toplevel_sections import ToplevelSections


class FrameSections(CTkFrame):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self._get_widgets()
self._place_widgets()

def _get_widgets(self) -> None:
self.label = CTkLabel(master=self, text="Sections")
self.listbox_sections = TreeviewSections(master=self)
self.button_load_sections = ButtonLoadSections(
master=self,
text="Load",
)
self.button_save_sections = ButtonSaveSections(master=self, text="Save")
self.button_new_section = ButtonNewSection(master=self, text="New")
self.button_delete_selected_sections = ButtonDeleteSelectedSections(
master=self, text="Remove"
)
self.button_edit_geometry_selected_section = (
ButtonUpdateSelectedSectionGeometry(master=self, text="Edit geometry")
)
self.button_edit_metadata_selected_section = (
ButtonUpdateSelectedSectionMetadata(master=self, text="Edit metadata")
)

def _place_widgets(self) -> None:
self.label.grid(row=0, column=0, padx=PADX, pady=PADY, sticky=STICKY)
self.button_load_sections.grid(
row=1, column=0, padx=PADX, pady=PADY, sticky=STICKY
)
self.button_save_sections.grid(
row=2, column=0, padx=PADX, pady=PADY, sticky=STICKY
)
self.listbox_sections.grid(row=3, column=0, padx=PADX, pady=PADY, sticky=STICKY)
self.button_new_section.grid(
row=4, column=0, padx=PADX, pady=PADY, sticky=STICKY
)
self.button_edit_geometry_selected_section.grid(
row=5, column=0, padx=PADX, pady=PADY, sticky=STICKY
)
self.button_edit_metadata_selected_section.grid(
row=6, column=0, padx=PADX, pady=PADY, sticky=STICKY
)
self.button_delete_selected_sections.grid(
row=7, column=0, padx=PADX, pady=PADY, sticky=STICKY
)


class TreeviewSections(Treeview):
def __init__(self, **kwargs: Any) -> None:
super().__init__(show="tree", **kwargs)
self.bind("<ButtonRelease-3>", self._deselect_sections)
self._define_columns()
# This call should come from outside later
sections = ["North", "West", "South", "East"]
self.add_sections(sections=sections)

def _define_columns(self) -> None:
self["columns"] = "Section"
self.column(column="#0", width=0)
self.column(column="Section", anchor="center", width=80, minwidth=40)
self["displaycolumns"] = "Section"

def add_sections(self, sections: list[str]) -> None:
for id, section in enumerate(sections):
self.insert(parent="", index="end", iid=str(id), text="", values=[section])

def _deselect_sections(self, event: Any) -> None:
for item in self.selection():
self.selection_remove(item)


class ListboxSections(Listbox):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)

# This call should come from outside later
sections = ["North", "West", "South", "East"]
self.show(sections=sections)

def show(self, sections: list[str]) -> None:
for i, section in enumerate(sections):
self.insert(i, section)


class ButtonLoadSections(CTkButton):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)

self.bind("<ButtonRelease-1>", self.on_click)

def on_click(self, events: Any) -> None:
self.sections_file = askopenfilename(
title="Load sections file", filetypes=[("sections file", "*.otflow")]
)
print(f"Sections file to load: {self.sections_file}")


class ButtonSaveSections(CTkButton):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)

self.bind("<ButtonRelease-1>", self.on_click)

def on_click(self, events: Any) -> None:
self.sections_file = asksaveasfilename(
title="Load sections file", filetypes=[("sections file", "*.otflow")]
)
print(f"Sections file to save: {self.sections_file}")


class ButtonNewSection(CTkButton):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)

self.toplevel_sections: ToplevelSections | None = None

self.bind("<ButtonRelease-1>", self.on_click)

self.toplevel_sections = None

def on_click(self, events: Any) -> None:
# TODO: Enter drawing mode
self.get_metadata()
# TODO: Yield geometry and metadata
print(
"Add new section with geometry = <TODO> and"
+ f"metadata = {self.section_metadata}"
)

def get_metadata(self) -> None:
if self.toplevel_sections is None or not self.toplevel_sections.winfo_exists():
self.toplevel_sections = ToplevelSections(title="New section")
else:
self.toplevel_sections.focus()
self.section_metadata = self.toplevel_sections.show()


class ButtonUpdateSelectedSectionGeometry(CTkButton):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)

self.bind("<ButtonRelease-1>", self.on_click)

def on_click(self, events: Any) -> None:
# TODO: Make sure only one section is selected
# TODO: Get currently selected section
# TODO: Enter drawing mode (there, old section is deleted, first)
# TODO: Yield updated geometry
print("Update geometry of selected section")


class ButtonUpdateSelectedSectionMetadata(CTkButton):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)

self.toplevel_sections: ToplevelSections | None = None

self.bind("<ButtonRelease-1>", self.on_click)

self.toplevel_sections = None

def on_click(self, events: Any) -> None:
# TODO: Make sure only one section is selected
# TODO: Get currently selected section
self.get_metadata()
# TODO: Yield updated metadata
print(f"Update selected section with metadata={self.section_metadata}")

def get_metadata(self) -> None:
# TODO: Retrieve sections metadata via ID from selection in Treeview
INPUT_VALUES: dict = {"name": "Existing Section"}
if self.toplevel_sections is None or not self.toplevel_sections.winfo_exists():
self.toplevel_sections = ToplevelSections(
title="New section", input_values=INPUT_VALUES
)
else:
self.toplevel_sections.focus()
self.section_metadata = self.toplevel_sections.show()


class ButtonDeleteSelectedSections(CTkButton):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)

self.bind("<ButtonRelease-1>", self.on_click)

def on_click(self, events: Any) -> None:
# TODO: Get currently selected sections (?)
print("Delete selected sections")
Loading