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

User story/2297 make distance of flows optional #221

Merged
merged 8 commits into from
Jun 13, 2023
15 changes: 5 additions & 10 deletions OTAnalytics/domain/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,37 +27,32 @@ class Flow:

Args:
id (FlowId): unique id of the flow
name (str): unique name of the flow
start (Section): section to start the flow at
end (Section): section to end the flow at
distance (float): distance between start and end
distance (Optional[float]): distance between start and end

Raises:
ValueError: if distance is negative
"""

id: FlowId
name: str
start: SectionId
end: SectionId
distance: float

def __init__(
self,
id: FlowId,
name: str,
start: SectionId,
end: SectionId,
distance: float,
distance: Optional[float] = None,
) -> None:
if distance < 0:
if distance and distance < 0:
raise ValueError(
f"Distance must be equal or greater then 0, but is {distance}"
)
self.id: FlowId = id
self.name = name
self.start: SectionId = start
self.end: SectionId = end
self.distance: float = distance
self.distance: Optional[float] = distance

def to_dict(self) -> dict:
return {
Expand Down
9 changes: 7 additions & 2 deletions OTAnalytics/plugin_parser/otvision_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Iterable, Sequence, Tuple
from typing import Any, Iterable, Optional, Sequence, Tuple

import ujson

Expand Down Expand Up @@ -610,7 +610,7 @@ def parse_flow(self, entry: dict) -> Flow:
name = entry.get(flow.FLOW_NAME, flow_id.id)
start = SectionId(entry.get(flow.START, ""))
end = SectionId(entry.get(flow.END, ""))
distance = float(entry.get(flow.DISTANCE, 0.0))
distance = self.__parse_distance(entry)
return Flow(
flow_id,
name=name,
Expand All @@ -619,6 +619,11 @@ def parse_flow(self, entry: dict) -> Flow:
distance=distance,
)

def __parse_distance(self, entry: dict) -> Optional[float]:
if distance_entry := entry.get(flow.DISTANCE, 0.0):
return float(distance_entry)
return None

def serialize(
self,
sections: Iterable[Section],
Expand Down
8 changes: 6 additions & 2 deletions OTAnalytics/plugin_ui/customtkinter_gui/dummy_viewmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ def add_flow(self) -> None:
name = flow_data[FLOW_NAME]
new_from_section_id = SectionId(flow_data[START_SECTION])
new_to_section_id = SectionId(flow_data[END_SECTION])
distance = float(flow_data[DISTANCE])
distance = flow_data.get(DISTANCE, None)
flow = Flow(
id=flow_id,
name=name,
Expand Down Expand Up @@ -575,8 +575,12 @@ def _show_distances_window(
initial_position=position,
section_ids=section_ids,
input_values=input_values,
show_distance=self._show_distance(),
).get_data()

def _show_distance(self) -> bool:
return True

def __to_id_resource(self, section: Section) -> IdResource:
return IdResource(id=section.id.serialize(), name=section.name)

Expand All @@ -585,7 +589,7 @@ def __update_flow_data(self, flow_data: dict) -> None:
name = flow_data[FLOW_NAME]
new_from_section_id = SectionId(flow_data[START_SECTION])
new_to_section_id = SectionId(flow_data[END_SECTION])
distance = float(flow_data[DISTANCE])
distance = flow_data.get(DISTANCE, None)
if flow := self._application.get_flow_for(flow_id):
flow.name = name
flow.start = new_from_section_id
Expand Down
31 changes: 21 additions & 10 deletions OTAnalytics/plugin_ui/customtkinter_gui/toplevel_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(
initial_position: tuple[int, int],
section_ids: list[IdResource],
input_values: dict | None = {},
show_distance: bool = True,
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
Expand All @@ -36,6 +37,7 @@ def __init__(
self._section_id_to_name = self._create_section_id_to_name(section_ids)
self._current_name = StringVar()
self.input_values: dict = self.__create_input_values(input_values)
self._show_distance = show_distance
self.protocol("WM_DELETE_WINDOW", self.close)
self._initial_position = initial_position
self._last_autofilled_name: str = ""
Expand Down Expand Up @@ -63,7 +65,7 @@ def __create_input_values(self, input_values: Optional[dict]) -> dict:
FLOW_NAME: "",
START_SECTION: "",
END_SECTION: "",
DISTANCE: 0,
DISTANCE: None,
}

def _get_widgets(self) -> None:
Expand Down Expand Up @@ -96,7 +98,8 @@ def _get_widgets(self) -> None:
validate="key",
validatecommand=(self.register(self._is_float_above_zero), "%P"),
)
self.entry_distance.insert(0, self.input_values[DISTANCE])
if current_distance := self.input_values[DISTANCE]:
self.entry_distance.insert(index=0, string=current_distance)

self.button_ok = CTkButton(master=self, text="Ok", command=self.close)

Expand All @@ -123,11 +126,16 @@ def _place_widgets(self) -> None:
self.dropdown_section_end.grid(row=1, column=1, padx=PADX, pady=PADY, sticky=W)
self.label_name.grid(row=2, column=0, padx=PADX, pady=PADY, sticky=tkinter.E)
self.entry_name.grid(row=2, column=1, padx=PADX, pady=PADY, sticky=tkinter.W)
self.label_distance.grid(row=3, column=0, padx=PADX, pady=PADY, sticky=E)
self.entry_distance.grid(row=3, column=1, padx=PADX, pady=PADY, sticky=W)
self.button_ok.grid(
row=4, column=0, columnspan=2, padx=PADX, pady=PADY, sticky=STICKY
)
if self._show_distance:
self.label_distance.grid(row=3, column=0, padx=PADX, pady=PADY, sticky=E)
self.entry_distance.grid(row=3, column=1, padx=PADX, pady=PADY, sticky=W)
self.button_ok.grid(
row=4, column=0, columnspan=2, padx=PADX, pady=PADY, sticky=STICKY
)
else:
self.button_ok.grid(
row=3, column=0, columnspan=2, padx=PADX, pady=PADY, sticky=STICKY
)

def _set_initial_position(self) -> None:
x, y = self._initial_position
Expand Down Expand Up @@ -158,7 +166,7 @@ def close(self, event: Any = None) -> None:
self.input_values[FLOW_NAME] = self._current_name.get()
self.input_values[START_SECTION] = self._get_start_section_id()
self.input_values[END_SECTION] = self._get_end_section_id()
self.input_values[DISTANCE] = self.entry_distance.get()
self.input_values[DISTANCE] = self.__parse_distance(self.entry_distance.get())
self.destroy()
self.update()

Expand All @@ -175,10 +183,13 @@ def _get_start_section_id(self) -> str:

def _is_float_above_zero(self, entry_value: Any) -> bool:
try:
float_value = float(entry_value)
float_value = self.__parse_distance(entry_value)
except Exception:
return False
return float_value >= 0
return float_value >= 0 if float_value else True

def __parse_distance(self, entry_value: Any) -> Optional[float]:
return float(entry_value) if entry_value else None

def _sections_are_valid(self) -> bool:
section_start = self._get_start_section_id()
Expand Down
10 changes: 10 additions & 0 deletions tests/OTAnalytics/domain/test_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ def flow() -> Flow:


class TestFlow:
def test_unset_distance(self) -> None:
flow_id = FlowId("1")
name = "some"
start = Mock(spec=Section)
end = Mock(spec=Section)

flow = Flow(flow_id, name, start=start, end=end)

assert not flow.distance

def test_invalid_distance(self) -> None:
flow_id = FlowId("1")
name = "some"
Expand Down
53 changes: 35 additions & 18 deletions tests/OTAnalytics/plugin_parser/test_otvision_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@ def assert_detection_equal(self, d1: Detection, d2: Detection) -> None:
assert d1.track_id == d2.track_id


class TestOtsectionParser:
def test_parse_section(self, test_data_tmp_dir: Path) -> None:
class TestOtFlowParser:
def test_parse_sections_and_flows(self, test_data_tmp_dir: Path) -> None:
first_coordinate = Coordinate(0, 0)
second_coordinate = Coordinate(1, 1)
third_coordinate = Coordinate(1, 0)
Expand Down Expand Up @@ -364,34 +364,51 @@ def test_parse_section(self, test_data_tmp_dir: Path) -> None:
first_coordinate,
],
)
flow_id = FlowId("1")
flow_name = "some to other"
flow_distance = 1
some_flow_id = FlowId("1")
some_flow_name = "some to other"
some_flow_distance = 1
some_flow = Flow(
flow_id,
name=flow_name,
some_flow_id,
name=some_flow_name,
start=line_section_id,
end=area_section_id,
distance=flow_distance,
distance=some_flow_distance,
)
json_file = test_data_tmp_dir / "section.json"
other_flow_id = FlowId("2")
other_flow_name = "other to some"
other_flow_distance = None
other_flow = Flow(
other_flow_id,
name=other_flow_name,
start=area_section_id,
end=line_section_id,
distance=other_flow_distance,
)
json_file = test_data_tmp_dir / "section.otflow"
json_file.touch()
sections = [line_section, area_section]
flows = [some_flow]
flows = [some_flow, other_flow]
parser = OtFlowParser()
parser.serialize(sections, flows, json_file)

parsed_sections, parsed_flows = parser.parse(json_file)

parsed_flow = parsed_flows[0]

assert parsed_sections == sections
assert len(parsed_flows) == 1
assert parsed_flow.id == flow_id
assert parsed_flow.name == flow_name
assert parsed_flow.start == line_section_id
assert parsed_flow.end == area_section_id
assert parsed_flow.distance == flow_distance
assert len(parsed_flows) == 2

some_parsed_flow = parsed_flows[0]
assert some_parsed_flow.id == some_flow_id
assert some_parsed_flow.name == some_flow_name
assert some_parsed_flow.start == line_section_id
assert some_parsed_flow.end == area_section_id
assert some_parsed_flow.distance == some_flow_distance

other_parsed_flow = parsed_flows[1]
assert other_parsed_flow.id == other_flow_id
assert other_parsed_flow.name == other_flow_name
assert other_parsed_flow.start == area_section_id
assert other_parsed_flow.end == line_section_id
assert other_parsed_flow.distance == other_flow_distance

def test_validate(self) -> None:
parser = OtFlowParser()
Expand Down