Skip to content

Commit

Permalink
Merge pull request #476 from OpenTrafficCam/task/4441-clean-up-otanal…
Browse files Browse the repository at this point in the history
…ytics-gui

task/4441-clean-up-otanalytics-gui
  • Loading branch information
randy-seng authored Mar 7, 2024
2 parents eb1956b + 31f7af6 commit 8bedc90
Show file tree
Hide file tree
Showing 16 changed files with 419 additions and 184 deletions.
8 changes: 8 additions & 0 deletions OTAnalytics/adapter_ui/view_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,14 @@ def next_frame(self) -> None:
def previous_frame(self) -> None:
pass

@abstractmethod
def next_second(self) -> None:
raise NotImplementedError

@abstractmethod
def previous_second(self) -> None:
raise NotImplementedError

@abstractmethod
def update_skip_time(self, seconds: int, frames: int) -> None:
raise NotImplementedError
Expand Down
23 changes: 13 additions & 10 deletions OTAnalytics/application/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@
TrackViewState,
VideosMetadata,
)
from OTAnalytics.application.ui.frame_control import (
SwitchToNextFrame,
SwitchToPreviousFrame,
)
from OTAnalytics.application.ui.frame_control import SwitchToNext, SwitchToPrevious
from OTAnalytics.application.use_cases.config import SaveOtconfig
from OTAnalytics.application.use_cases.create_events import (
CreateEvents,
Expand Down Expand Up @@ -102,8 +99,8 @@ def __init__(
start_new_project: StartNewProject,
project_updater: ProjectUpdater,
load_track_files: LoadTrackFiles,
previous_frame: SwitchToPreviousFrame,
next_frame: SwitchToNextFrame,
previous_frame: SwitchToPrevious,
next_frame: SwitchToNext,
) -> None:
self._datastore: Datastore = datastore
self.track_state: TrackState = track_state
Expand Down Expand Up @@ -135,8 +132,8 @@ def __init__(
self._track_repository_size = TrackRepositorySize(
self._datastore._track_repository
)
self._previous_frame = previous_frame
self._next_frame = next_frame
self._switch_previous = previous_frame
self._switch_next = next_frame

def connect_observers(self) -> None:
"""
Expand Down Expand Up @@ -458,10 +455,16 @@ def get_current_track_offset(self) -> Optional[RelativeOffsetCoordinate]:
return self.track_view_state.track_offset.get()

def next_frame(self) -> None:
self._next_frame.set_next_frame()
self._switch_next.switch_frame()

def previous_frame(self) -> None:
self._previous_frame.set_previous_frame()
self._switch_previous.switch_frame()

def next_second(self) -> None:
self._switch_next.switch_second()

def previous_second(self) -> None:
self._switch_previous.switch_second()

def update_date_range_tracks_filter(self, date_range: DateRange) -> None:
"""Update the date range of the track filter.
Expand Down
15 changes: 15 additions & 0 deletions OTAnalytics/application/plotting.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, timedelta
from math import floor
from typing import Any, Callable, Generic, Iterable, Optional, Sequence, TypeVar
Expand Down Expand Up @@ -51,6 +52,20 @@ def reset(self) -> None:
raise NotImplementedError


@dataclass(frozen=True)
class LayerGroup:
name: str
layers: Sequence[Layer]

def register(self, observer: Callable[[bool], None]) -> None:
for layer in self.layers:
layer.register(observer)

def reset(self) -> None:
for layer in self.layers:
layer.reset()


class PlottingLayer(Plotter, Layer):
def __init__(self, name: str, other: Plotter, enabled: bool) -> None:
self._name = name
Expand Down
26 changes: 13 additions & 13 deletions OTAnalytics/application/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def __init__(self) -> None:
self.selected_videos: ObservableProperty[list[Video]] = ObservableProperty[
list[Video]
](default=[])
self.skip_time = ObservableProperty[SkipTime](SkipTime(1, 0))
self.skip_time = ObservableProperty[SkipTime](SkipTime(1, 1))

def reset(self) -> None:
"""Reset to default settings."""
Expand Down Expand Up @@ -251,9 +251,9 @@ class SectionState(SectionListObserver):
"""

def __init__(self, get_sections_by_id: GetSectionsById) -> None:
self.selected_sections: ObservableProperty[
list[SectionId]
] = ObservableProperty[list]([])
self.selected_sections: ObservableProperty[list[SectionId]] = (
ObservableProperty[list]([])
)
self._get_sections_by_id = get_sections_by_id

def notify_sections(self, section_event: SectionRepositoryEvent) -> None:
Expand Down Expand Up @@ -471,18 +471,18 @@ def __init__(
self._track_repository = track_repository
self._include_classes = include_classes
self._exclude_classes = exclude_classes
self._first_detection_occurrence: ObservableOptionalProperty[
datetime
] = ObservableOptionalProperty[datetime]()
self._last_detection_occurrence: ObservableOptionalProperty[
datetime
] = ObservableOptionalProperty[datetime]()
self._first_detection_occurrence: ObservableOptionalProperty[datetime] = (
ObservableOptionalProperty[datetime]()
)
self._last_detection_occurrence: ObservableOptionalProperty[datetime] = (
ObservableOptionalProperty[datetime]()
)
self._classifications: ObservableProperty[frozenset[str]] = ObservableProperty[
frozenset
](frozenset())
self._detection_classifications: ObservableProperty[
frozenset[str]
] = ObservableProperty[frozenset](frozenset([]))
self._detection_classifications: ObservableProperty[frozenset[str]] = (
ObservableProperty[frozenset](frozenset([]))
)

@property
def first_detection_occurrence(self) -> Optional[datetime]:
Expand Down
79 changes: 47 additions & 32 deletions OTAnalytics/application/ui/frame_control.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
from abc import ABC, abstractmethod
from datetime import timedelta

from OTAnalytics.application.state import TrackViewState, VideosMetadata
from OTAnalytics.domain.date import DateRange


class SwitchToNextFrame:
class SwitchTo(ABC):
def __init__(self, state: TrackViewState, videos_metadata: VideosMetadata) -> None:
self._state = state
self._videos_metadata = videos_metadata

def set_next_frame(self) -> None:
def switch_second(self) -> None:
skip_time = self._state.skip_time.get()
current_skip = timedelta(seconds=skip_time.seconds)
self._switch_time(current_skip)

def switch_frame(self) -> None:
if current_skip := self._get_current_skip_frames():
self._switch_time(current_skip)

def _get_current_skip_frames(self) -> timedelta | None:
if filter_element := self._state.filter_element.get():
current_date_range = filter_element.date_range
if current_date_range.start_date and current_date_range.end_date:
Expand All @@ -20,38 +30,43 @@ def set_next_frame(self) -> None:
skip_time = self._state.skip_time.get()
subseconds = min(skip_time.frames, fps) / fps
milliseconds = subseconds * 1000
current_skip = timedelta(
seconds=skip_time.seconds, milliseconds=milliseconds
)
next_start = current_date_range.start_date + current_skip
next_end = current_date_range.end_date + current_skip
next_date_range = DateRange(next_start, next_end)
self._state.filter_element.set(
filter_element.derive_date(next_date_range)
)


class SwitchToPreviousFrame:
current_skip = timedelta(milliseconds=milliseconds)
return current_skip
return None

@abstractmethod
def _switch_time(self, current_skip: timedelta) -> None:
raise NotImplementedError


class SwitchToNext(SwitchTo):

def __init__(self, state: TrackViewState, videos_metadata: VideosMetadata) -> None:
self._state = state
self._videos_metadata = videos_metadata
super().__init__(state, videos_metadata)

def set_previous_frame(self) -> None:
def _switch_time(self, current_skip: timedelta) -> None:
if filter_element := self._state.filter_element.get():
current_date_range = filter_element.date_range
if current_date_range.start_date and current_date_range.end_date:
if metadata := self._videos_metadata.get_metadata_for(
current_date_range.end_date
):
fps = metadata.fps
skip_time = self._state.skip_time.get()
subseconds = min(skip_time.frames, fps) / fps
current_skip = timedelta(seconds=skip_time.seconds) + timedelta(
seconds=subseconds
)
next_start = current_date_range.start_date - current_skip
next_end = current_date_range.end_date - current_skip
next_date_range = DateRange(next_start, next_end)
self._state.filter_element.set(
filter_element.derive_date(next_date_range)
)
next_start = current_date_range.start_date + current_skip
next_end = current_date_range.end_date + current_skip
next_date_range = DateRange(next_start, next_end)
self._state.filter_element.set(
filter_element.derive_date(next_date_range)
)


class SwitchToPrevious(SwitchTo):
def __init__(self, state: TrackViewState, videos_metadata: VideosMetadata) -> None:
super().__init__(state, videos_metadata)

def _switch_time(self, current_skip: timedelta) -> None:
if filter_element := self._state.filter_element.get():
current_date_range = filter_element.date_range
if current_date_range.start_date and current_date_range.end_date:
next_start = current_date_range.start_date - current_skip
next_end = current_date_range.end_date - current_skip
next_date_range = DateRange(next_start, next_end)
self._state.filter_element.set(
filter_element.derive_date(next_date_range)
)
33 changes: 31 additions & 2 deletions OTAnalytics/plugin_ui/customtkinter_gui/custom_containers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Any
import tkinter
from typing import Any, Protocol

from customtkinter import CTkFrame, CTkScrollableFrame, CTkTabview

Expand Down Expand Up @@ -38,7 +39,7 @@ def __init__(
self,
**kwargs: Any,
) -> None:
super().__init__(border_width=0, **kwargs)
super().__init__(border_width=0, height=10, **kwargs)
self.set_fg_color_as_segmented_button_color()
self._configure_grid()

Expand Down Expand Up @@ -85,6 +86,34 @@ def _configure_grid(self) -> None:
self.grid_columnconfigure(0, weight=1)


class FrameFactory(Protocol):
def __call__(self, **kwargs: Any) -> CTkFrame:
raise NotImplementedError


class SingleFrameTabview(CustomCTkTabview):
def __init__(
self,
frame_factory: FrameFactory,
title: str,
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
self._frame_factory = frame_factory
self._title = title
self._get_widgets()
self._place_widgets()
self.disable_segmented_button()

def _get_widgets(self) -> None:
self.add(self._title)
self.frame = self._frame_factory(master=self.tab(self._title))

def _place_widgets(self) -> None:
self.frame.pack(fill=tkinter.BOTH, expand=True)
self.set(self._title)


class CTkEmbeddedTabview(CTkTabview):
def __init__(
self,
Expand Down
12 changes: 9 additions & 3 deletions OTAnalytics/plugin_ui/customtkinter_gui/dummy_viewmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1362,6 +1362,12 @@ def next_frame(self) -> None:
def previous_frame(self) -> None:
self._application.previous_frame()

def next_second(self) -> None:
self._application.next_second()

def previous_second(self) -> None:
self._application.previous_second()

def validate_date(self, date: str) -> bool:
return any(
[validate_date(date, date_format) for date_format in SUPPORTED_FORMATS]
Expand Down Expand Up @@ -1498,9 +1504,9 @@ def export_counts(self) -> None:
"There is no flow configurated.\n"
"Please create a flow."
),
initial_position=self._window.get_position()
if self._window
else (0, 0),
initial_position=(
self._window.get_position() if self._window else (0, 0)
),
)
return
export_formats: dict = {
Expand Down
2 changes: 0 additions & 2 deletions OTAnalytics/plugin_ui/customtkinter_gui/frame_bbox_offset.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def _get_widgets(self) -> None:
self.label_y_value = CTkLabel(master=self, width=20)
self.slider_x = CTkSlider(
master=self,
width=110,
from_=0,
to=1,
number_of_steps=1 / SLIDER_RESOLUTION,
Expand All @@ -42,7 +41,6 @@ def _get_widgets(self) -> None:
self.slider_x.bind("<ButtonRelease-1>", self._on_slider_release)
self.slider_y = CTkSlider(
master=self,
width=110,
from_=0,
to=1,
number_of_steps=10,
Expand Down
8 changes: 3 additions & 5 deletions OTAnalytics/plugin_ui/customtkinter_gui/frame_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ def __init__(self, viewmodel: ViewModel, **kwargs: Any) -> None:
self._introduce_to_viewmodel()

def _get_widgets(self) -> None:
self.label = CTkLabel(master=self, text="Visualization Filters")
self.filter_by_date_button = FilterTracksByDateFilterButton(
master=self,
name="Filter By Date",
Expand All @@ -91,15 +90,14 @@ def _get_widgets(self) -> None:
)

def _place_widgets(self) -> None:
self.label.grid(row=0, column=0, padx=PADX, pady=PADY, sticky=STICKY_WEST)
self.filter_by_date_button.grid(
row=1, column=0, padx=(PADX, 0), pady=PADY, sticky=STICKY
row=0, column=0, padx=(PADX, 0), pady=PADY, sticky=STICKY
)
self.date_range_switcher.grid(
row=1, column=1, padx=(0, PADX), pady=PADY, sticky=STICKY
row=0, column=1, padx=(0, PADX), pady=PADY, sticky=STICKY
)
self.filter_by_classification_button.grid(
row=1, column=2, padx=PADX, pady=PADY, sticky=STICKY
row=0, column=2, padx=PADX, pady=PADY, sticky=STICKY
)

def reset(self) -> None:
Expand Down
Loading

0 comments on commit 8bedc90

Please sign in to comment.