Skip to content

Commit 8fe3b8c

Browse files
authored
Merge pull request #849 from OpenTrafficCam/bug/10484-missing-assignment-of-tracks-to-flows
Bug/10484 missing assignment of tracks to flows
2 parents 79e0cd0 + e021cda commit 8fe3b8c

11 files changed

Lines changed: 80 additions & 119 deletions

File tree

‎OTAnalytics/adapter_ui/dummy_viewmodel.py‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -476,9 +476,6 @@ def _update_enabled_video_control_buttons(self) -> None:
476476
def _on_section_changed(self, section: SectionId) -> None:
477477
self._refresh_sections_in_ui()
478478

479-
def on_flow_changed(self, flow_id: FlowId) -> None:
480-
self.notify_flows([flow_id])
481-
482479
def on_background_updated(self, image: Optional[TrackImage]) -> None:
483480
if image:
484481
self.frame_canvas.update_background(image)

‎OTAnalytics/adapter_ui/view_model.py‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,6 @@ def get_tracks_assigned_to_each_flow(self) -> dict[FlowId, int]:
461461
async def export_track_statistics(self) -> None:
462462
raise NotImplementedError
463463

464-
@abstractmethod
465-
def on_flow_changed(self, flow_id: FlowId) -> None:
466-
raise NotImplementedError
467-
468464
@abstractmethod
469465
def on_background_updated(self, image: Optional[TrackImage]) -> None:
470466
raise NotImplementedError

‎OTAnalytics/application/application.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
from OTAnalytics.application.use_cases.update_project import ProjectUpdater
8787
from OTAnalytics.domain.date import DateRange
8888
from OTAnalytics.domain.filter import FilterElement, FilterElementSettingRestorer
89-
from OTAnalytics.domain.flow import Flow, FlowChangedObserver, FlowId, FlowListObserver
89+
from OTAnalytics.domain.flow import Flow, FlowId, FlowListObserver
9090
from OTAnalytics.domain.geometry import RelativeOffsetCoordinate
9191
from OTAnalytics.domain.section import (
9292
Section,
@@ -238,9 +238,6 @@ def register_section_changed_observer(
238238
def register_flows_observer(self, observer: FlowListObserver) -> None:
239239
self._datastore.register_flows_observer(observer)
240240

241-
def register_flow_changed_observer(self, observer: FlowChangedObserver) -> None:
242-
self._datastore.register_flow_changed_observer(observer)
243-
244241
def get_all_sections(self) -> list[Section]:
245242
return self._datastore.get_all_sections()
246243

‎OTAnalytics/application/datastore.py‎

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,7 @@
1010
EventListExporter,
1111
)
1212
from OTAnalytics.domain.event import Event, EventRepository
13-
from OTAnalytics.domain.flow import (
14-
Flow,
15-
FlowChangedObserver,
16-
FlowId,
17-
FlowListObserver,
18-
FlowRepository,
19-
)
13+
from OTAnalytics.domain.flow import Flow, FlowId, FlowListObserver, FlowRepository
2014
from OTAnalytics.domain.progress import ProgressbarBuilder
2115
from OTAnalytics.domain.remark import RemarkRepository
2216
from OTAnalytics.domain.section import (
@@ -301,15 +295,6 @@ def register_section_changed_observer(
301295
"""
302296
self._section_repository.register_section_changed_observer(observer)
303297

304-
def register_flow_changed_observer(self, observer: FlowChangedObserver) -> None:
305-
"""
306-
Listen to changes of sections in the repository.
307-
308-
Args:
309-
observer (FlowChangedObserver): observer to notify about changes
310-
"""
311-
self._flow_repository.register_flow_changed_observer(observer)
312-
313298
def update_section(self, section: Section) -> None:
314299
"""
315300
Update the section in the repository.

‎OTAnalytics/application/use_cases/assignment_repository.py‎

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from OTAnalytics.application.use_cases.create_road_user_assignments import (
77
CreateRoadUserAssignments,
88
)
9-
from OTAnalytics.application.use_cases.flow_repository import GetAllFlows
109
from OTAnalytics.domain.event import EventRepository, EventRepositoryEvent
1110
from OTAnalytics.domain.flow import FlowId, FlowListObserver
1211

@@ -70,34 +69,22 @@ def __call__(self, repo_event: EventRepositoryEvent) -> None:
7069
self._assignment_repository.remove_assignments_of_event(event)
7170

7271

73-
class RemoveAssignmentsOfChangedFlow: # is a FlowChangedObserver
74-
def __init__(self, assignment_repository: RoadUserAssignmentRepository) -> None:
75-
self._assignment_repository = assignment_repository
76-
77-
def __call__(self, flow: FlowId) -> None:
78-
self._assignment_repository.remove_assignments_of_flow(flow)
72+
class ClearAssignmentsOnChangedFlows(FlowListObserver):
73+
"""Clear all assignments.
7974
75+
Args:
76+
assignment_repository (RoadUserAssignmentRepository): the repository
77+
holding the assignments to clear.
78+
"""
8079

81-
class RemoveAssignmentsOfRemovedFlows(FlowListObserver):
82-
def __init__(
83-
self,
84-
assignment_repository: RoadUserAssignmentRepository,
85-
get_all_flows: GetAllFlows,
86-
) -> None:
80+
def __init__(self, assignment_repository: RoadUserAssignmentRepository) -> None:
8781
self._assignment_repository = assignment_repository
88-
self._get_all_flows = get_all_flows
8982

9083
def notify_flows(self, flows: list[FlowId]) -> None:
91-
# TODO notification [] means flow is removed or all are removed!
92-
# FlowRepositoryEvent would be helpful here
93-
if not flows:
94-
observed_flows = self._assignment_repository.get_observed_flows()
95-
current_flows = [flow.id for flow in self._get_all_flows.get()]
96-
97-
if len(current_flows) == 0:
98-
self._assignment_repository.clear()
99-
return
100-
101-
gen = (flow for flow in observed_flows if flow not in current_flows)
102-
for flow in gen:
103-
self._assignment_repository.remove_assignments_of_flow(flow)
84+
"""Clear all assignments after flows have been changed.
85+
86+
Args:
87+
flows (list[FlowId]): the added or updated flows, empty if flows
88+
have been removed.
89+
"""
90+
self._assignment_repository.clear()

‎OTAnalytics/domain/flow.py‎

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from abc import ABC, abstractmethod
22
from dataclasses import dataclass
3-
from typing import Callable, Generic, Iterable, Optional, TypeVar
3+
from typing import Iterable, Optional
44

55
from OTAnalytics.domain.section import SectionId
66

@@ -84,44 +84,15 @@ class FlowListObserver(ABC):
8484
@abstractmethod
8585
def notify_flows(self, flows: list[FlowId]) -> None:
8686
"""
87-
Notifies that the given flows have been added or removed.
87+
Notifies that the given flows have been added or updated.
8888
89-
Args:
90-
flows (list[FlowId]): list of added or removed flows
91-
"""
92-
pass
93-
94-
95-
VALUE = TypeVar("VALUE")
96-
97-
FlowChangedObserver = Callable[[FlowId], None]
98-
99-
100-
class FlowChangedSubject(Generic[VALUE]):
101-
"""
102-
Helper class to handle and notify observers
103-
"""
104-
105-
def __init__(self) -> None:
106-
self.observers: set[FlowChangedObserver] = set()
107-
108-
def register(self, observer: FlowChangedObserver) -> None:
109-
"""
110-
Listen to events.
111-
112-
Args:
113-
observer (FlowChangedObserver): listener to add
114-
"""
115-
self.observers.add(observer)
116-
117-
def notify(self, value: FlowId) -> None:
118-
"""
119-
Notifies observers about the changed value.
89+
An empty list indicates that flows have been removed.
12090
12191
Args:
122-
value (FlowId): changed value
92+
flows (list[FlowId]): list of added or updated flows, empty if flows
93+
have been removed
12394
"""
124-
[observer(value) for observer in self.observers]
95+
pass
12596

12697

12798
class FlowListSubject:
@@ -146,7 +117,8 @@ def notify(self, flows: list[FlowId]) -> None:
146117
Notifies observers about the list of flows.
147118
148119
Args:
149-
flows (list[FlowId]): list of added flows
120+
flows (list[FlowId]): list of added or updated flows, empty if flows
121+
have been removed
150122
"""
151123
[observer.notify_flows(flows) for observer in self.observers]
152124

@@ -156,14 +128,10 @@ def __init__(self) -> None:
156128
self._flows: dict[FlowId, Flow] = {}
157129
self._current_id = 0
158130
self._repository_content_observers: FlowListSubject = FlowListSubject()
159-
self._flow_content_observers: FlowChangedSubject = FlowChangedSubject()
160131

161132
def register_flows_observer(self, observer: FlowListObserver) -> None:
162133
self._repository_content_observers.register(observer)
163134

164-
def register_flow_changed_observer(self, observer: FlowChangedObserver) -> None:
165-
self._flow_content_observers.register(observer)
166-
167135
def get_id(self) -> FlowId:
168136
"""
169137
Get an id for a new flow
@@ -219,7 +187,7 @@ def remove(self, flow_id: FlowId) -> None:
219187

220188
def update(self, flow: Flow) -> None:
221189
self._flows[flow.id] = flow
222-
self._flow_content_observers.notify(flow.id)
190+
self._repository_content_observers.notify([flow.id])
223191

224192
def get(self, flow_id: FlowId) -> Optional[Flow]:
225193
return self._flows.get(flow_id)

‎OTAnalytics/plugin_prototypes/track_visualization/track_viz.py‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,24 @@ def __init__(
9797
super().__init__(plotter_factory, flow_state.selected_flows, self.get_flow_ids)
9898

9999
flow_repository.register_flows_observer(self)
100-
flow_repository.register_flow_changed_observer(self.notify_flow)
101100
track_repository.observers.register(self.notify_invalidate)
102101
event_repository.register_observer(self.notify_events)
103102

104103
self._repository = flow_repository
105104
self._flow_id_resolver = flow_id_resolver
106105

107-
def notify_flow(self, flow: FlowId) -> None:
108-
self.notify_layers_changed([flow])
109-
110106
def notify_flows(self, flows: list[FlowId]) -> None:
107+
"""Handle added, updated or removed flows.
108+
109+
All layers are invalidated because road users are assigned to exactly
110+
one flow. Changing a single flow can therefore change the tracks
111+
plotted for every other flow as well.
112+
113+
Args:
114+
flows (list[FlowId]): the added or updated flows, empty if flows
115+
have been removed.
116+
"""
117+
self.notify_invalidate(None)
111118
self.notify_layers_changed(flows)
112119

113120
def notify_events(self, events: EventRepositoryEvent) -> None:

‎OTAnalytics/plugin_ui/base_application.py‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,9 @@
6262
from OTAnalytics.application.use_cases.add_new_remark import AddNewRemark
6363
from OTAnalytics.application.use_cases.apply_cli_cuts import ApplyCliCuts
6464
from OTAnalytics.application.use_cases.assignment_repository import (
65+
ClearAssignmentsOnChangedFlows,
6566
GetRoadUserAssignments,
66-
RemoveAssignmentsOfChangedFlow,
6767
RemoveAssignmentsOfRemovedEvents,
68-
RemoveAssignmentsOfRemovedFlows,
6968
)
7069
from OTAnalytics.application.use_cases.clear_repositories import ClearRepositories
7170
from OTAnalytics.application.use_cases.config import SaveOtconfig
@@ -971,14 +970,8 @@ def remove_assignments_of_removed_events(self) -> RemoveAssignmentsOfRemovedEven
971970
)
972971

973972
@cached_property
974-
def remove_assignments_of_removed_flows(self) -> RemoveAssignmentsOfRemovedFlows:
975-
return RemoveAssignmentsOfRemovedFlows(
976-
self.assignment_repository, self.get_all_flows
977-
)
978-
979-
@cached_property
980-
def remove_assignments_of_changed_flows(self) -> RemoveAssignmentsOfChangedFlow:
981-
return RemoveAssignmentsOfChangedFlow(self.assignment_repository)
973+
def clear_assignments_on_changed_flows(self) -> ClearAssignmentsOnChangedFlows:
974+
return ClearAssignmentsOnChangedFlows(self.assignment_repository)
982975

983976
@cached_property
984977
def get_all_assignments(self) -> GetRoadUserAssignments:

‎OTAnalytics/plugin_ui/gui_application.py‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ def register_observers(self) -> None:
6767
self.application.register_video_observer(self.view_model)
6868
self.application.register_sections_observer(self.view_model)
6969
self.application.register_flows_observer(self.view_model)
70-
self.application.register_flow_changed_observer(self.view_model.on_flow_changed)
7170
self.track_view_state.selected_videos.register(
7271
self.view_model.update_selected_videos
7372
)
@@ -127,15 +126,12 @@ def register_observers(self) -> None:
127126
# configure observers for count plot saver
128127
self.track_view_state.count_plots.register(self.save_count_plots.save)
129128

130-
# configure assignment repository update on flow/event deletion
129+
# configure assignment repository update on flow and event changes
131130
self.event_repository.register_observer(
132131
self.remove_assignments_of_removed_events
133132
)
134-
self.flow_repository.register_flow_changed_observer(
135-
self.remove_assignments_of_changed_flows
136-
)
137133
self.flow_repository.register_flows_observer(
138-
self.remove_assignments_of_removed_flows
134+
self.clear_assignments_on_changed_flows
139135
)
140136

141137
@cached_property
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from unittest.mock import Mock
2+
3+
from OTAnalytics.application.analysis.road_user_assignment import (
4+
RoadUserAssignmentRepository,
5+
)
6+
from OTAnalytics.application.use_cases.assignment_repository import (
7+
ClearAssignmentsOnChangedFlows,
8+
)
9+
from OTAnalytics.domain.flow import FlowId
10+
11+
12+
class TestClearAssignmentsOnChangedFlows:
13+
def test_clear_on_changed_flow(self) -> None:
14+
assignment_repository = Mock(spec=RoadUserAssignmentRepository)
15+
target = ClearAssignmentsOnChangedFlows(assignment_repository)
16+
17+
target.notify_flows([FlowId("1")])
18+
19+
assignment_repository.clear.assert_called_once()
20+
21+
def test_clear_on_added_flows(self) -> None:
22+
assignment_repository = Mock(spec=RoadUserAssignmentRepository)
23+
target = ClearAssignmentsOnChangedFlows(assignment_repository)
24+
25+
target.notify_flows([FlowId("1"), FlowId("2")])
26+
27+
assignment_repository.clear.assert_called_once()
28+
29+
def test_clear_on_removed_flows(self) -> None:
30+
assignment_repository = Mock(spec=RoadUserAssignmentRepository)
31+
target = ClearAssignmentsOnChangedFlows(assignment_repository)
32+
33+
target.notify_flows([])
34+
35+
assignment_repository.clear.assert_called_once()

0 commit comments

Comments
 (0)