From 84d43ba4be9b91f22f25657ed2fc5244a37937d6 Mon Sep 17 00:00:00 2001 From: Armin Kollascheck Date: Fri, 19 Jul 2024 18:28:13 +0200 Subject: [PATCH 1/7] zwischenstand --- OTGroundTruther/gui/gui.py | 16 +++++++++++++++- OTGroundTruther/presenter/presenter.py | 6 ++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/OTGroundTruther/gui/gui.py b/OTGroundTruther/gui/gui.py index b0c2bf1..23ae084 100644 --- a/OTGroundTruther/gui/gui.py +++ b/OTGroundTruther/gui/gui.py @@ -1,6 +1,7 @@ from typing import Any import customtkinter as ctk +from CTkMessagebox import CTkMessagebox from OTGroundTruther.gui.constants import PADX, PADY, tk_events from OTGroundTruther.gui.frame_canvas import FrameCanvas @@ -46,13 +47,26 @@ def _place_widgets(self) -> None: side=ctk.RIGHT, fill=ctk.BOTH, expand=True, padx=PADX, pady=PADY ) - def build_key_assignment_window(self, key_assignment_text: dict[str, str]): + def build_key_assignment_window(self, key_assignment_text: dict[str, str]) -> None: self.key_assigment_window = KeyAssignmentWindow( master=self, key_assignment_text=key_assignment_text, presenter=self._presenter, ) + def ask_if_keep_existing_counts(self) -> bool: + msg = CTkMessagebox( + title="Keep existing Counts?", + message="Do you want to keep the already existing counts?", + icon="question", + option_1="Yes", + option_2="No", + ) + if msg == "Yes": + return True + else: + return False + class GuiEventTranslator: def __init__(self, gui: Gui, presenter: PresenterInterface): diff --git a/OTGroundTruther/presenter/presenter.py b/OTGroundTruther/presenter/presenter.py index dc5a4fa..3f016c4 100644 --- a/OTGroundTruther/presenter/presenter.py +++ b/OTGroundTruther/presenter/presenter.py @@ -82,8 +82,10 @@ def load_events(self) -> None: ], ) if output_askfile: - self._model.read_sections_from_file(Path(output_askfile)) - self._model.read_events_from_file(Path(output_askfile)) + if self._model._count_repository.get_all_as_dict: + keep = self._gui.ask_if_keep_existing_counts() + self._model.read_sections_from_file(Path(output_askfile), keep) + self._model.read_events_from_file(Path(output_askfile), keep) self.refresh_treeview() if self._current_frame is None: return From 78fca806e4e08218cf66c78cd5490bdefe63a9ef Mon Sep 17 00:00:00 2001 From: Armin Kollascheck Date: Tue, 27 Aug 2024 18:17:13 +0200 Subject: [PATCH 2/7] current standing, fixed question if keep --- OTGroundTruther/gui/gui.py | 24 +++++++- OTGroundTruther/gui/presenter_interface.py | 4 ++ OTGroundTruther/model/count.py | 13 +++-- OTGroundTruther/model/model.py | 33 ++++++++--- OTGroundTruther/model/section.py | 22 +++++++- .../presenter/model_initializer.py | 4 +- OTGroundTruther/presenter/presenter.py | 52 +++++++++++++++--- requirements.txt | Bin 244 -> 284 bytes 8 files changed, 127 insertions(+), 25 deletions(-) diff --git a/OTGroundTruther/gui/gui.py b/OTGroundTruther/gui/gui.py index 23ae084..94dc23b 100644 --- a/OTGroundTruther/gui/gui.py +++ b/OTGroundTruther/gui/gui.py @@ -57,16 +57,36 @@ def build_key_assignment_window(self, key_assignment_text: dict[str, str]) -> No def ask_if_keep_existing_counts(self) -> bool: msg = CTkMessagebox( title="Keep existing Counts?", - message="Do you want to keep the already existing counts?", + message="Do you want to keep the already existing counts and sections?", icon="question", option_1="Yes", option_2="No", ) - if msg == "Yes": + keep_existing = msg.get() + print(keep_existing) + if keep_existing == "Yes": return True else: return False + def get_new_suffix_for_new_counts(self): + print("hello") + self.subwindow = ctk.CTkToplevel(self) + self.subwindow.title("Suffix for the counts of the file") + ctk.CTkLabel( + self.subwindow, text="Enter the suffix for the counts of the file." + ) + self.entry = ctk.CTkEntry(self.subwindow) + self.entry.pack(pady=5) + ctk.CTkButton( + self.subwindow, text="OK", command=self.get_suffix_and_add_counts + ).pack(pady=10) + + def get_suffix_and_add_counts(self) -> None: + user_input = self.entry.get() + self.subwindow.destroy() + self._presenter.load_counts_with_suffix(suffix=user_input) + class GuiEventTranslator: def __init__(self, gui: Gui, presenter: PresenterInterface): diff --git a/OTGroundTruther/gui/presenter_interface.py b/OTGroundTruther/gui/presenter_interface.py index fd7d1be..9b291dd 100644 --- a/OTGroundTruther/gui/presenter_interface.py +++ b/OTGroundTruther/gui/presenter_interface.py @@ -75,3 +75,7 @@ def show_class_image_by_count_id(self, count_id: str) -> None: @abstractmethod def show_key_assignment(self) -> None: raise NotImplementedError + + @abstractmethod + def load_counts_with_suffix(self, suffix: str) -> None: + raise NotImplementedError diff --git a/OTGroundTruther/model/count.py b/OTGroundTruther/model/count.py index 9d6bef2..72d2aa7 100644 --- a/OTGroundTruther/model/count.py +++ b/OTGroundTruther/model/count.py @@ -287,7 +287,12 @@ def to_event_list(self) -> list[EventForParsingSerializing]: event_list.append(event_for_save) return event_list - def from_event_list(self, event_list: list[EventForParsingSerializing]) -> None: + def from_event_list( + self, + event_list: list[EventForParsingSerializing], + keep_existing_events: bool, + suffix: str, + ) -> None: """ create count list from event list and the suitable list of the object ids set current id = 0 (only important for id naming of new events later) if the @@ -297,11 +302,11 @@ def from_event_list(self, event_list: list[EventForParsingSerializing]) -> None: event_list (list[Event_For_Saving]): List of events. """ events, classes = self._get_events_and_classes_by_id(event_list) - - self.clear() + if not keep_existing_events: + self.clear() for id_ in events.keys(): if len(events[id_]) >= 2: - self._counts[id_] = Count( + self._counts[id_ + suffix] = Count( road_user_id=id_, events=events[id_], road_user_class=classes[id_], diff --git a/OTGroundTruther/model/model.py b/OTGroundTruther/model/model.py index 4efd26e..5abb341 100644 --- a/OTGroundTruther/model/model.py +++ b/OTGroundTruther/model/model.py @@ -1,5 +1,6 @@ import datetime as dt from pathlib import Path +from typing import Sequence from OTGroundTruther.gui.constants import tk_events from OTGroundTruther.gui.key_assignment import ( @@ -65,21 +66,37 @@ def load_videos_from_files(self, files: list[Path]): self._video_repository.add_all(videos) print(f"Videos loaded: {files}") - def read_sections_from_file(self, file: Path) -> None: - self._section_repository.clear() + def read_sections_from_file(self, file: Path) -> tuple[bool, Sequence[LineSection]]: + self.last_file_path = file sections, otanalytics_file_content = self._section_parser.parse(file=file) - self._section_repository.add_all(sections) self._section_repository.set_otanalytics_file_content(otanalytics_file_content) - print(f"Sections read from {file}") + if self._count_repository.get_all_as_dict(): + print(f"Check compatibility sections {file}") + compatible, gates_already_existed = ( + self._section_repository.check_if_compatible(sections) + ) + else: + compatible = True + return compatible, sections - def read_events_from_file(self, file: Path) -> None: + def add_sections(self, sections, keep_existing_sections: bool): + if not keep_existing_sections: + self._section_repository.clear() + self._section_repository.add_all(sections) + print("Sections added") + + def read_events_from_file(self, keep_existing_events: bool, suffix: str) -> None: event_list = self._eventlistparser.parse( - events_file=file, + events_file=self.last_file_path, sections=self._section_repository.to_dict(), valid_road_user_classes=self._valid_road_user_classes, ) - self._count_repository.from_event_list(event_list) - print(f"Events read from {file}") + self._count_repository.from_event_list( + event_list=event_list, + keep_existing_events=keep_existing_events, + suffix=suffix, + ) + print(f"Events read from {self.last_file_path}") def write_events_and_sections_to_file( self, diff --git a/OTGroundTruther/model/section.py b/OTGroundTruther/model/section.py index f45e00a..6b18f99 100644 --- a/OTGroundTruther/model/section.py +++ b/OTGroundTruther/model/section.py @@ -122,7 +122,24 @@ def __init__(self) -> None: self._sections: dict[str, LineSection] = {} self._otanalytics_file_content: dict = {} - def add_all(self, sections: Iterable[LineSection]) -> None: + def check_if_compatible( + self, sections: Iterable[LineSection] + ) -> tuple[bool, dict[str, dict[str, bool]]]: + gates_already_existed = {} + compatible = True + for section in sections: + + id_already_exist = section.id in list(self._sections.keys()) + is_equal = id_already_exist and self._sections[section.id] == section + if id_already_exist and not is_equal: + compatible = False + gates_already_existed[section.id] = { + "id_already_exist": id_already_exist, + "is_equal": is_equal, + } + return (compatible, gates_already_existed) + + def add_all(self, sections: Iterable[LineSection]): """Add several sections at once to the repository. Args: @@ -130,9 +147,10 @@ def add_all(self, sections: Iterable[LineSection]) -> None: """ for section in sections: self._add(section) + # TODO: Check if ellipses around different sections touch each other - def _add(self, section: LineSection) -> None: + def _add(self, section: LineSection): """Internal method to add sections without notifying observers. Args: diff --git a/OTGroundTruther/presenter/model_initializer.py b/OTGroundTruther/presenter/model_initializer.py index 3e99b07..d781a9b 100644 --- a/OTGroundTruther/presenter/model_initializer.py +++ b/OTGroundTruther/presenter/model_initializer.py @@ -60,7 +60,9 @@ def _prefill_count_repository(self, file: Path) -> None: sections=self._section_repository.to_dict(), valid_road_user_classes=self._valid_road_user_classes, ) - self._count_repository.from_event_list(event_list) + self._count_repository.from_event_list( + event_list, keep_existing_events=False, suffix="_" + ) def get(self) -> Model: return self._model diff --git a/OTGroundTruther/presenter/presenter.py b/OTGroundTruther/presenter/presenter.py index 3f016c4..f235d64 100644 --- a/OTGroundTruther/presenter/presenter.py +++ b/OTGroundTruther/presenter/presenter.py @@ -1,5 +1,6 @@ from pathlib import Path from tkinter.filedialog import askopenfilename, askopenfilenames, asksaveasfilename +from typing import Sequence from OTGroundTruther.gui.gui import Gui from OTGroundTruther.gui.presenter_interface import PresenterInterface @@ -13,6 +14,7 @@ from OTGroundTruther.model.count import MissingRoadUserClassError, TooFewEventsError from OTGroundTruther.model.model import Model from OTGroundTruther.model.overlayed_frame import OverlayedFrame +from OTGroundTruther.model.section import LineSection MAX_SCROLL_STEP: int = 50 @@ -82,14 +84,48 @@ def load_events(self) -> None: ], ) if output_askfile: - if self._model._count_repository.get_all_as_dict: - keep = self._gui.ask_if_keep_existing_counts() - self._model.read_sections_from_file(Path(output_askfile), keep) - self._model.read_events_from_file(Path(output_askfile), keep) - self.refresh_treeview() - if self._current_frame is None: - return - self._refresh_current_frame() + new_sections, keep_existing_s_c = self.load_sections(output_askfile) + print(keep_existing_s_c) + + self._model.add_sections( + sections=new_sections, keep_existing_sections=keep_existing_s_c + ) + if not keep_existing_s_c: + self._model.read_events_from_file( + keep_existing_events=keep_existing_s_c, suffix="" + ) + self.refresh_treeview() + if self._current_frame is None: + return + self._refresh_current_frame() + else: + self._gui.get_new_suffix_for_new_counts() + + def load_sections(self, output_askfile: str) -> tuple[Sequence[LineSection], bool]: + compatible, new_sections = self._model.read_sections_from_file( + Path(output_askfile) + ) + if self._model._count_repository.get_all_as_dict(): + if compatible: + keep_existing_s_c = self._gui.ask_if_keep_existing_counts() + print("yolo") + print(keep_existing_s_c) + else: + print( + "The sections from the file are not compatible with the existing " + + "sections. Therefore the existing sections and counts got deleted" + ) + keep_existing_s_c = False + else: + keep_existing_s_c = False + return new_sections, keep_existing_s_c + + def load_counts_with_suffix(self, suffix: str) -> None: + self._model.read_events_from_file(keep_existing_events=True, suffix=suffix) + self.refresh_treeview() + if self._current_frame is None: + return + self._refresh_current_frame() def save_events(self) -> None: first_video_file = ( diff --git a/requirements.txt b/requirements.txt index 15a0e9647909693a0ad236db3e060cd49d9dd686..58ae5533fc7374ab3f0d3a082deb9b201822a4f1 100644 GIT binary patch delta 47 ycmeyuIEQJ%7Y%2I5Qc1qT!vJJVjxUpNC&c#81fk^7;J&ih(V9RoPn2tiva*B4hfh5 delta 6 NcmbQk^o4Q47XS&L0~i1R From a7ea005d623d9de32c2c6220bc33fd3cdda391bb Mon Sep 17 00:00:00 2001 From: Armin Kollascheck Date: Thu, 29 Aug 2024 15:32:19 +0200 Subject: [PATCH 3/7] fixing otflow loading --- OTGroundTruther/gui/frame_treeview_counts.py | 2 + OTGroundTruther/gui/gui.py | 85 ++++++++++++++----- OTGroundTruther/model/count.py | 8 +- .../presenter/model_initializer.py | 2 +- OTGroundTruther/presenter/presenter.py | 20 +++-- 5 files changed, 84 insertions(+), 33 deletions(-) diff --git a/OTGroundTruther/gui/frame_treeview_counts.py b/OTGroundTruther/gui/frame_treeview_counts.py index e291241..215f574 100644 --- a/OTGroundTruther/gui/frame_treeview_counts.py +++ b/OTGroundTruther/gui/frame_treeview_counts.py @@ -202,6 +202,8 @@ def add_next_column_sort_direction(self): def refresh_treeview(self, count_repository: CountRepository) -> None: self.delete(*self.get_children()) + if not count_repository.get_all_as_dict(): + return selected_classes = self._presenter.get_selected_classes_from_gui() for count in list(count_repository.get_all_as_dict().values()): if count.get_road_user_class().get_name() in selected_classes: diff --git a/OTGroundTruther/gui/gui.py b/OTGroundTruther/gui/gui.py index 94dc23b..4dcb9d8 100644 --- a/OTGroundTruther/gui/gui.py +++ b/OTGroundTruther/gui/gui.py @@ -13,6 +13,25 @@ TITLE: str = "OTGroundTruther" DELETE_BUTTON_TXT: str = "Delete" +SUBW_KEEPEXISTINGCOUNTS_TITLE: str = "Keep existing Counts?" +SUBW_KEEPEXISTINGCOUNTS_QUESTION: str = ( + "Do you want to keep the already existing counts and sections?" +) +SUBW_KEEPEXISTINGCOUNTS_ICON: str = "question" +SUBW_KEEPEXISTINGCOUNTS_KEEP: str = "Yes" +SUBW_KEEPEXISTINGCOUNTS_CLEAR: str = "No" + +SUBW_ENTERSUFFIXCOUNTS_TITLE: str = "Suffix for the counts of the file" +SUBW_ENTERSUFFIXCOUNTS_INSTRUCTION: str = "Enter a suffix for the counts of the file." + + +SUBW_SECTIONS_NOT_COMPARTIBLE_TITLE: str = "Sections are not compatible." +SUBW_SECTIONS_NOT_COMPARTIBLE_INFO: str = ( + "The sections from the file are not compatible with the existing " + + "sections. Therefore the existing sections and counts got deleted." +) +SUBW_SECTIONS_NOT_COMPARTIBLE_ICON: str = "info" + class Gui(ctk.CTk): def __init__(self, presenter: PresenterInterface, **kwargs: Any) -> None: @@ -56,36 +75,33 @@ def build_key_assignment_window(self, key_assignment_text: dict[str, str]) -> No def ask_if_keep_existing_counts(self) -> bool: msg = CTkMessagebox( - title="Keep existing Counts?", - message="Do you want to keep the already existing counts and sections?", - icon="question", - option_1="Yes", - option_2="No", + master=self, + title=SUBW_KEEPEXISTINGCOUNTS_TITLE, + message=SUBW_KEEPEXISTINGCOUNTS_QUESTION, + icon=SUBW_KEEPEXISTINGCOUNTS_ICON, + option_1=SUBW_KEEPEXISTINGCOUNTS_CLEAR, + option_2=SUBW_KEEPEXISTINGCOUNTS_KEEP, ) keep_existing = msg.get() - print(keep_existing) - if keep_existing == "Yes": + if keep_existing == SUBW_KEEPEXISTINGCOUNTS_KEEP: return True else: return False - def get_new_suffix_for_new_counts(self): - print("hello") - self.subwindow = ctk.CTkToplevel(self) - self.subwindow.title("Suffix for the counts of the file") - ctk.CTkLabel( - self.subwindow, text="Enter the suffix for the counts of the file." + def get_new_suffix_for_new_counts(self) -> None: + self.subwindow = EnteringStringSubwindow( + self, + title=SUBW_ENTERSUFFIXCOUNTS_TITLE, + label=SUBW_ENTERSUFFIXCOUNTS_INSTRUCTION, ) - self.entry = ctk.CTkEntry(self.subwindow) - self.entry.pack(pady=5) - ctk.CTkButton( - self.subwindow, text="OK", command=self.get_suffix_and_add_counts - ).pack(pady=10) - def get_suffix_and_add_counts(self) -> None: - user_input = self.entry.get() - self.subwindow.destroy() - self._presenter.load_counts_with_suffix(suffix=user_input) + def inform_user_sections_not_compatible(self) -> None: + self.subwindow = CTkMessagebox( + master=self, + title=SUBW_SECTIONS_NOT_COMPARTIBLE_TITLE, + message=SUBW_SECTIONS_NOT_COMPARTIBLE_INFO, + icon=SUBW_SECTIONS_NOT_COMPARTIBLE_ICON, + ) class GuiEventTranslator: @@ -100,3 +116,28 @@ def _bind_events(self) -> None: def _on_delete_key(self, event: Any) -> None: self._presenter.delete_selected_counts() + + +class EnteringStringSubwindow(ctk.CTkToplevel): + def __init__(self, gui: Gui, title: str, label: str) -> None: + super().__init__(gui) + self._gui = gui + self.title(title) + ctk.CTkLabel(self, text=label).pack(pady=0) + self.entry = ctk.CTkEntry(self) + self.entry.pack(pady=5) + ctk.CTkButton(self, text="OK", command=self._get_suffix_and_add_counts).pack( + pady=10 + ) + self.protocol("WM_DELETE_WINDOW", self.on_close) + self.transient(gui) + self.grab_set() + gui.wait_window(self) + + def _get_suffix_and_add_counts(self) -> None: + user_input = self.entry.get() + self.destroy() + self._gui._presenter.load_counts_with_suffix(suffix=user_input) + + def on_close(self): + self.destroy() diff --git a/OTGroundTruther/model/count.py b/OTGroundTruther/model/count.py index 72d2aa7..d842722 100644 --- a/OTGroundTruther/model/count.py +++ b/OTGroundTruther/model/count.py @@ -304,10 +304,16 @@ def from_event_list( events, classes = self._get_events_and_classes_by_id(event_list) if not keep_existing_events: self.clear() + elif bool(set(events.keys()) & set(self._counts.keys())): + self.clear() + print( + "Duplication of at least one counting ID. All already existing" + + " counts have been removed." + ) for id_ in events.keys(): if len(events[id_]) >= 2: self._counts[id_ + suffix] = Count( - road_user_id=id_, + road_user_id=id_ + suffix, events=events[id_], road_user_class=classes[id_], ) diff --git a/OTGroundTruther/presenter/model_initializer.py b/OTGroundTruther/presenter/model_initializer.py index d781a9b..4a18901 100644 --- a/OTGroundTruther/presenter/model_initializer.py +++ b/OTGroundTruther/presenter/model_initializer.py @@ -61,7 +61,7 @@ def _prefill_count_repository(self, file: Path) -> None: valid_road_user_classes=self._valid_road_user_classes, ) self._count_repository.from_event_list( - event_list, keep_existing_events=False, suffix="_" + event_list, keep_existing_events=False, suffix="" ) def get(self) -> Model: diff --git a/OTGroundTruther/presenter/presenter.py b/OTGroundTruther/presenter/presenter.py index f235d64..b4bdacd 100644 --- a/OTGroundTruther/presenter/presenter.py +++ b/OTGroundTruther/presenter/presenter.py @@ -70,7 +70,12 @@ def load_otflow(self) -> None: ], ) if output_askfile: - self._model.read_sections_from_file(Path(output_askfile)) + new_sections, keep_existing_s_c = self.load_sections(output_askfile) + + self._model.add_sections( + sections=new_sections, keep_existing_sections=keep_existing_s_c + ) + self.refresh_treeview() if self._current_frame is None: return self._refresh_current_frame() @@ -85,7 +90,6 @@ def load_events(self) -> None: ) if output_askfile: new_sections, keep_existing_s_c = self.load_sections(output_askfile) - print(keep_existing_s_c) self._model.add_sections( sections=new_sections, keep_existing_sections=keep_existing_s_c @@ -105,16 +109,14 @@ def load_sections(self, output_askfile: str) -> tuple[Sequence[LineSection], boo compatible, new_sections = self._model.read_sections_from_file( Path(output_askfile) ) - if self._model._count_repository.get_all_as_dict(): + if ( + self._model._count_repository.get_all_as_dict() + or self._model._section_repository.to_dict() + ): if compatible: keep_existing_s_c = self._gui.ask_if_keep_existing_counts() - print("yolo") - print(keep_existing_s_c) else: - print( - "The sections from the file are not compatible with the existing " - + "sections. Therefore the existing sections and counts got deleted" - ) + self._gui.inform_user_sections_not_compatible() keep_existing_s_c = False else: keep_existing_s_c = False From 6052130cf316f336a5b8627224e1d50f2c617c96 Mon Sep 17 00:00:00 2001 From: Armin Kollascheck Date: Thu, 29 Aug 2024 18:30:04 +0200 Subject: [PATCH 4/7] change structure of loading sections and counts --- OTGroundTruther/gui/gui.py | 29 ++++-- OTGroundTruther/gui/menu.py | 4 +- OTGroundTruther/gui/presenter_interface.py | 10 +- OTGroundTruther/model/count.py | 30 +++--- OTGroundTruther/model/model.py | 7 +- .../presenter/model_initializer.py | 6 +- OTGroundTruther/presenter/presenter.py | 94 ++++++++++--------- 7 files changed, 104 insertions(+), 76 deletions(-) diff --git a/OTGroundTruther/gui/gui.py b/OTGroundTruther/gui/gui.py index 4dcb9d8..2c42db3 100644 --- a/OTGroundTruther/gui/gui.py +++ b/OTGroundTruther/gui/gui.py @@ -25,12 +25,19 @@ SUBW_ENTERSUFFIXCOUNTS_INSTRUCTION: str = "Enter a suffix for the counts of the file." -SUBW_SECTIONS_NOT_COMPARTIBLE_TITLE: str = "Sections are not compatible." -SUBW_SECTIONS_NOT_COMPARTIBLE_INFO: str = ( +SUBW_SECTIONS_NOT_COMPATIBLE_TITLE: str = "Sections are not compatible." +SUBW_SECTIONS_NOT_COMPATIBLE_INFO: str = ( "The sections from the file are not compatible with the existing " + "sections. Therefore the existing sections and counts got deleted." ) -SUBW_SECTIONS_NOT_COMPARTIBLE_ICON: str = "info" +SUBW_SECTIONS_NOT_COMPATIBLE_ICON: str = "info" + +SUBW_COUNTS_NOT_COMPATIBLE_TITLE: str = "Counts are not compatible." +SUBW_COUNTS_NOT_COMPATIBLE_INFO: str = ( + "Duplication of at least one counting ID. All already existing" + + " counts have been removed." +) +SUBW_COUNTS_NOT_COMPATIBLE_ICON: str = "info" class Gui(ctk.CTk): @@ -98,9 +105,17 @@ def get_new_suffix_for_new_counts(self) -> None: def inform_user_sections_not_compatible(self) -> None: self.subwindow = CTkMessagebox( master=self, - title=SUBW_SECTIONS_NOT_COMPARTIBLE_TITLE, - message=SUBW_SECTIONS_NOT_COMPARTIBLE_INFO, - icon=SUBW_SECTIONS_NOT_COMPARTIBLE_ICON, + title=SUBW_SECTIONS_NOT_COMPATIBLE_TITLE, + message=SUBW_SECTIONS_NOT_COMPATIBLE_INFO, + icon=SUBW_SECTIONS_NOT_COMPATIBLE_ICON, + ) + + def inform_user_counts_not_compatible(self) -> None: + self.subwindow = CTkMessagebox( + master=self, + title=SUBW_COUNTS_NOT_COMPATIBLE_TITLE, + message=SUBW_COUNTS_NOT_COMPATIBLE_INFO, + icon=SUBW_COUNTS_NOT_COMPATIBLE_ICON, ) @@ -137,7 +152,7 @@ def __init__(self, gui: Gui, title: str, label: str) -> None: def _get_suffix_and_add_counts(self) -> None: user_input = self.entry.get() self.destroy() - self._gui._presenter.load_counts_with_suffix(suffix=user_input) + self._gui._presenter.add_new_counts(keep_existing_s_c=True, suffix=user_input) def on_close(self): self.destroy() diff --git a/OTGroundTruther/gui/menu.py b/OTGroundTruther/gui/menu.py index 52ccfb5..aa8b103 100644 --- a/OTGroundTruther/gui/menu.py +++ b/OTGroundTruther/gui/menu.py @@ -29,7 +29,9 @@ def __init__(self, presenter: PresenterInterface, **kwargs: Any): def _get_and_place_widgets(self) -> None: self.add_command(label="Load Videos", command=self._presenter.load_video_files) self.add_command(label="Load otflow", command=self._presenter.load_otflow) - self.add_command(label="Load Events", command=self._presenter.load_events) + self.add_command( + label="Load Events", command=self._presenter.load_otevents_otgtevents + ) self.add_command(label="Save Events", command=self._presenter.save_events) diff --git a/OTGroundTruther/gui/presenter_interface.py b/OTGroundTruther/gui/presenter_interface.py index 9b291dd..925f864 100644 --- a/OTGroundTruther/gui/presenter_interface.py +++ b/OTGroundTruther/gui/presenter_interface.py @@ -15,7 +15,11 @@ def load_otflow(self) -> None: raise NotImplementedError @abstractmethod - def load_events(self) -> None: + def load_otevents_otgtevents(self) -> None: + raise NotImplementedError + + @abstractmethod + def add_new_counts(self, keep_existing_s_c: bool, suffix: str) -> None: raise NotImplementedError @abstractmethod @@ -75,7 +79,3 @@ def show_class_image_by_count_id(self, count_id: str) -> None: @abstractmethod def show_key_assignment(self) -> None: raise NotImplementedError - - @abstractmethod - def load_counts_with_suffix(self, suffix: str) -> None: - raise NotImplementedError diff --git a/OTGroundTruther/model/count.py b/OTGroundTruther/model/count.py index d842722..8d3ffd4 100644 --- a/OTGroundTruther/model/count.py +++ b/OTGroundTruther/model/count.py @@ -287,12 +287,11 @@ def to_event_list(self) -> list[EventForParsingSerializing]: event_list.append(event_for_save) return event_list - def from_event_list( + def event_list_to_count_dict( self, event_list: list[EventForParsingSerializing], - keep_existing_events: bool, suffix: str, - ) -> None: + ) -> tuple[bool, dict[str, Count]]: """ create count list from event list and the suitable list of the object ids set current id = 0 (only important for id naming of new events later) if the @@ -302,26 +301,33 @@ def from_event_list( event_list (list[Event_For_Saving]): List of events. """ events, classes = self._get_events_and_classes_by_id(event_list) - if not keep_existing_events: - self.clear() - elif bool(set(events.keys()) & set(self._counts.keys())): - self.clear() - print( - "Duplication of at least one counting ID. All already existing" - + " counts have been removed." - ) + counts = {} for id_ in events.keys(): if len(events[id_]) >= 2: - self._counts[id_ + suffix] = Count( + counts[id_ + suffix] = Count( road_user_id=id_ + suffix, events=events[id_], road_user_class=classes[id_], ) else: continue # TODO: Store in "SingleEventRepository" + compatible = not self.ids_already_exist_in_counts(counts=counts) + return compatible, counts + + def add_new_counts( + self, + new_counts: dict[str, Count], + keep_existing_counts: bool, + ) -> None: + if not keep_existing_counts: + self.clear() + self._counts = self._counts | new_counts if len(self._counts.keys()) > 0: self.set_current_id(list(self._counts.keys())[-1]) + def ids_already_exist_in_counts(self, counts: dict[str, Count]): + return bool(set(counts.keys()) & set(self._counts.keys())) + def _get_events_and_classes_by_id( self, event_list: list[EventForParsingSerializing] ) -> tuple[dict[str, list[Event]], dict[str, RoadUserClass]]: diff --git a/OTGroundTruther/model/model.py b/OTGroundTruther/model/model.py index 5abb341..186ac15 100644 --- a/OTGroundTruther/model/model.py +++ b/OTGroundTruther/model/model.py @@ -85,18 +85,19 @@ def add_sections(self, sections, keep_existing_sections: bool): self._section_repository.add_all(sections) print("Sections added") - def read_events_from_file(self, keep_existing_events: bool, suffix: str) -> None: + def read_events_from_file(self, suffix: str) -> tuple[bool, dict[str, Count]]: event_list = self._eventlistparser.parse( events_file=self.last_file_path, sections=self._section_repository.to_dict(), valid_road_user_classes=self._valid_road_user_classes, ) - self._count_repository.from_event_list( + + (compatible, counts) = self._count_repository.event_list_to_count_dict( event_list=event_list, - keep_existing_events=keep_existing_events, suffix=suffix, ) print(f"Events read from {self.last_file_path}") + return (compatible, counts) def write_events_and_sections_to_file( self, diff --git a/OTGroundTruther/presenter/model_initializer.py b/OTGroundTruther/presenter/model_initializer.py index 4a18901..df73e06 100644 --- a/OTGroundTruther/presenter/model_initializer.py +++ b/OTGroundTruther/presenter/model_initializer.py @@ -60,9 +60,11 @@ def _prefill_count_repository(self, file: Path) -> None: sections=self._section_repository.to_dict(), valid_road_user_classes=self._valid_road_user_classes, ) - self._count_repository.from_event_list( - event_list, keep_existing_events=False, suffix="" + (compatible, counts) = self._count_repository.event_list_to_count_dict( + event_list=event_list, + suffix="", ) + self._count_repository.add_new_counts(counts, keep_existing_counts=False) def get(self) -> Model: return self._model diff --git a/OTGroundTruther/presenter/presenter.py b/OTGroundTruther/presenter/presenter.py index b4bdacd..b2f758b 100644 --- a/OTGroundTruther/presenter/presenter.py +++ b/OTGroundTruther/presenter/presenter.py @@ -1,6 +1,5 @@ from pathlib import Path from tkinter.filedialog import askopenfilename, askopenfilenames, asksaveasfilename -from typing import Sequence from OTGroundTruther.gui.gui import Gui from OTGroundTruther.gui.presenter_interface import PresenterInterface @@ -14,7 +13,8 @@ from OTGroundTruther.model.count import MissingRoadUserClassError, TooFewEventsError from OTGroundTruther.model.model import Model from OTGroundTruther.model.overlayed_frame import OverlayedFrame -from OTGroundTruther.model.section import LineSection + +# from OTGroundTruther.model.section import LineSection MAX_SCROLL_STEP: int = 50 @@ -69,18 +69,15 @@ def load_otflow(self) -> None: ("otevents", f"*{OTANALYTICS_FILE_SUFFIX}"), ], ) - if output_askfile: - new_sections, keep_existing_s_c = self.load_sections(output_askfile) - - self._model.add_sections( - sections=new_sections, keep_existing_sections=keep_existing_s_c - ) - self.refresh_treeview() - if self._current_frame is None: - return - self._refresh_current_frame() + if not output_askfile: + return + self._read_compare_add_sections(output_askfile) + self.refresh_treeview() + if self._current_frame is None: + return + self._refresh_current_frame() - def load_events(self) -> None: + def load_otevents_otgtevents(self) -> None: output_askfile = askopenfilename( defaultextension=f"*{GROUND_TRUTH_EVENTS_FILE_SUFFIX}", filetypes=[ @@ -88,47 +85,52 @@ def load_events(self) -> None: ("otevents", f"*{OTEVENTS_FILE_SUFFIX}"), ], ) - if output_askfile: - new_sections, keep_existing_s_c = self.load_sections(output_askfile) - - self._model.add_sections( - sections=new_sections, keep_existing_sections=keep_existing_s_c - ) - if not keep_existing_s_c: - self._model.read_events_from_file( - keep_existing_events=keep_existing_s_c, suffix="" - ) - self.refresh_treeview() - if self._current_frame is None: - return - self._refresh_current_frame() - else: - self._gui.get_new_suffix_for_new_counts() - - def load_sections(self, output_askfile: str) -> tuple[Sequence[LineSection], bool]: - compatible, new_sections = self._model.read_sections_from_file( - Path(output_askfile) - ) - if ( - self._model._count_repository.get_all_as_dict() - or self._model._section_repository.to_dict() - ): - if compatible: - keep_existing_s_c = self._gui.ask_if_keep_existing_counts() - else: - self._gui.inform_user_sections_not_compatible() - keep_existing_s_c = False + if not output_askfile: + return + keep_existing_s_c = self._read_compare_add_sections(output_askfile) + if keep_existing_s_c: + self._gui.get_new_suffix_for_new_counts() else: + self.add_new_counts(keep_existing_s_c, suffix="") + + def _read_compare_add_sections(self, output_askfile: str) -> bool: + ( + sections_compatible, + new_sections, + ) = self._model.read_sections_from_file(Path(output_askfile)) + + if sections_compatible and self.counts_or_sections_already_exist(): + keep_existing_s_c = self._gui.ask_if_keep_existing_counts() + elif not sections_compatible and self.counts_or_sections_already_exist(): + self._gui.inform_user_sections_not_compatible() keep_existing_s_c = False - return new_sections, keep_existing_s_c + else: + keep_existing_s_c = False + + self._model.add_sections( + sections=new_sections, keep_existing_sections=keep_existing_s_c + ) + + return keep_existing_s_c - def load_counts_with_suffix(self, suffix: str) -> None: - self._model.read_events_from_file(keep_existing_events=True, suffix=suffix) + def add_new_counts(self, keep_existing_s_c: bool, suffix: str) -> None: + counts_compatible, new_counts = self._model.read_events_from_file(suffix=suffix) + if not counts_compatible and keep_existing_s_c: + self._gui.inform_user_counts_not_compatible() + keep_existing_s_c = False + self._model._count_repository.add_new_counts( + new_counts, keep_existing_counts=keep_existing_s_c + ) self.refresh_treeview() if self._current_frame is None: return self._refresh_current_frame() + def counts_or_sections_already_exist(self): + return bool(self._model._count_repository.get_all_as_dict()) or bool( + self._model._section_repository.to_dict() + ) + def save_events(self) -> None: first_video_file = ( self._model._video_repository.get_first_video().get_filepath() From 877db28af001676b8e2bb508cd869253a3d84fde Mon Sep 17 00:00:00 2001 From: Armin Kollascheck Date: Thu, 29 Aug 2024 18:37:04 +0200 Subject: [PATCH 5/7] missing typings --- OTGroundTruther/model/model.py | 6 ++++-- OTGroundTruther/model/section.py | 2 +- OTGroundTruther/presenter/presenter.py | 4 +--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/OTGroundTruther/model/model.py b/OTGroundTruther/model/model.py index 186ac15..90938f6 100644 --- a/OTGroundTruther/model/model.py +++ b/OTGroundTruther/model/model.py @@ -1,6 +1,6 @@ import datetime as dt from pathlib import Path -from typing import Sequence +from typing import Iterable, Sequence from OTGroundTruther.gui.constants import tk_events from OTGroundTruther.gui.key_assignment import ( @@ -79,7 +79,9 @@ def read_sections_from_file(self, file: Path) -> tuple[bool, Sequence[LineSectio compatible = True return compatible, sections - def add_sections(self, sections, keep_existing_sections: bool): + def add_sections( + self, sections: Iterable[LineSection], keep_existing_sections: bool + ): if not keep_existing_sections: self._section_repository.clear() self._section_repository.add_all(sections) diff --git a/OTGroundTruther/model/section.py b/OTGroundTruther/model/section.py index 6b18f99..8a231f2 100644 --- a/OTGroundTruther/model/section.py +++ b/OTGroundTruther/model/section.py @@ -139,7 +139,7 @@ def check_if_compatible( } return (compatible, gates_already_existed) - def add_all(self, sections: Iterable[LineSection]): + def add_all(self, sections: Iterable[LineSection]) -> None: """Add several sections at once to the repository. Args: diff --git a/OTGroundTruther/presenter/presenter.py b/OTGroundTruther/presenter/presenter.py index b2f758b..8bf258b 100644 --- a/OTGroundTruther/presenter/presenter.py +++ b/OTGroundTruther/presenter/presenter.py @@ -14,8 +14,6 @@ from OTGroundTruther.model.model import Model from OTGroundTruther.model.overlayed_frame import OverlayedFrame -# from OTGroundTruther.model.section import LineSection - MAX_SCROLL_STEP: int = 50 @@ -126,7 +124,7 @@ def add_new_counts(self, keep_existing_s_c: bool, suffix: str) -> None: return self._refresh_current_frame() - def counts_or_sections_already_exist(self): + def counts_or_sections_already_exist(self) -> bool: return bool(self._model._count_repository.get_all_as_dict()) or bool( self._model._section_repository.to_dict() ) From ab4cf3fedc9a1d4ff4c3f2a291789df70cb464f4 Mon Sep 17 00:00:00 2001 From: Armin Kollascheck Date: Tue, 3 Dec 2024 14:29:54 +0100 Subject: [PATCH 6/7] later use function --- OTGroundTruther/presenter/presenter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OTGroundTruther/presenter/presenter.py b/OTGroundTruther/presenter/presenter.py index 8bf258b..dce68be 100644 --- a/OTGroundTruther/presenter/presenter.py +++ b/OTGroundTruther/presenter/presenter.py @@ -294,3 +294,6 @@ def show_key_assignment(self) -> None: self._gui.build_key_assignment_window( key_assignment_text=self._model.get_key_assignment_text() ) + + def later_use(self) -> None: + self._current_frame = 0 From 9d578cde702d22f27f9367ecdc5d50002a0bd618 Mon Sep 17 00:00:00 2001 From: Armin Kollascheck Date: Tue, 3 Dec 2024 14:41:26 +0100 Subject: [PATCH 7/7] fix --- OTGroundTruther/presenter/presenter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OTGroundTruther/presenter/presenter.py b/OTGroundTruther/presenter/presenter.py index dce68be..49085fd 100644 --- a/OTGroundTruther/presenter/presenter.py +++ b/OTGroundTruther/presenter/presenter.py @@ -296,4 +296,5 @@ def show_key_assignment(self) -> None: ) def later_use(self) -> None: - self._current_frame = 0 + + self.update_canvas_image_with_new_overlay()