Skip to content

Commit a387758

Browse files
authored
Merge pull request #741 from OpenTrafficCam/customer-reqs/8596-speed-up-otanalytics-by-using-vectorized-operations
Customer reqs/8596 speed up otanalytics by using vectorized operations
2 parents 8a39002 + 81aae18 commit a387758

95 files changed

Lines changed: 7123 additions & 1709 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.pre-commit-config.yaml‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,30 @@ repos:
4545
entry: mypy OTAnalytics tests --config-file=pyproject.toml
4646
additional_dependencies:
4747
- av==13.0.0
48-
- black==25.1.0
48+
- black==24.8.0
4949
- customtkinter==5.2.2
50-
- fastapi==0.115.13
50+
- fastapi==0.116.2
5151
- hatch-requirements-txt==0.4.1
5252
- ijson==3.3.0
5353
- interrogate==1.7.0
54-
- isort==6.0.1
54+
- isort==5.13.2
5555
- matplotlib==3.10.0
56-
- more-itertools==10.5.0
56+
- more-itertools==10.7.0
5757
- mypy==1.16.1
58-
- nicegui==2.20.0
58+
- nicegui==2.24.1
5959
- numpy==2.1.1
6060
- opencv-python-headless==4.10.0.84
6161
- pandas-stubs==2.2.3.241126
6262
- pandas==2.2.3
63+
- polars==1.32.3
6364
- pre-commit==4.1.0
65+
- pyarrow==21.0.0
6466
- pytest-asyncio==0.24.0
6567
- pytest-benchmark==5.1.0
6668
- pytest-cov==6.0.0
6769
- pytest-lazy-fixtures==1.1.2
6870
- pytest==8.3.4
69-
- starlette==0.45.3
71+
- starlette==0.48.0
7072
- twine==6.1.0
7173
- types-PyYAML
7274
- types-flake8

‎OTAnalytics/adapter_ui/dummy_viewmodel.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,6 +1330,7 @@ async def remove_flows(self) -> None:
13301330
)
13311331

13321332
def create_events(self) -> None:
1333+
logger().info("Creating events")
13331334
start_msg_popup = self._ui_factory.minimal_info_box(
13341335
message="Create events...",
13351336
initial_position=self.get_position(),
@@ -1343,6 +1344,7 @@ def create_events(self) -> None:
13431344
start_msg_popup.update_message(message="Creating events completed")
13441345
sleep(1)
13451346
start_msg_popup.close()
1347+
logger().info("Events created")
13461348

13471349
def get_all_flow_ids(self) -> list[FlowId]:
13481350
return [flow.id for flow in self.get_all_flows()]

‎OTAnalytics/application/analysis/intersect.py‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,27 @@
22
from collections import defaultdict
33
from typing import Iterable, Mapping
44

5-
from OTAnalytics.domain.event import Event
5+
from OTAnalytics.domain.event import EventDataset
66
from OTAnalytics.domain.geometry import RelativeOffsetCoordinate
77
from OTAnalytics.domain.section import Section, SectionId
8-
from OTAnalytics.domain.track import TrackId
8+
from OTAnalytics.domain.track_dataset.track_dataset import TrackIdSet
99
from OTAnalytics.domain.types import EventType
1010

1111

12-
class IntersectionError(Exception):
13-
pass
14-
15-
1612
class RunIntersect(ABC):
1713
"""
1814
Interface defining the use case to intersect the given tracks with the given
1915
sections.
2016
"""
2117

2218
@abstractmethod
23-
def __call__(self, sections: Iterable[Section]) -> list[Event]:
19+
def __call__(self, sections: Iterable[Section]) -> EventDataset:
2420
raise NotImplementedError
2521

2622

2723
class TracksIntersectingSections(ABC):
2824
@abstractmethod
29-
def __call__(self, sections: Iterable[Section]) -> dict[SectionId, set[TrackId]]:
25+
def __call__(self, sections: Iterable[Section]) -> dict[SectionId, TrackIdSet]:
3026
raise NotImplementedError
3127

3228

‎OTAnalytics/application/analysis/traffic_counting.py‎

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,15 @@
1919
from OTAnalytics.application.export_formats.export_mode import ExportMode
2020
from OTAnalytics.application.use_cases.create_events import CreateEvents
2121
from OTAnalytics.application.use_cases.section_repository import GetSectionsById
22-
from OTAnalytics.domain.event import Event, EventRepository
22+
from OTAnalytics.domain.event import (
23+
Event,
24+
EventDataset,
25+
EventRepository,
26+
PythonEventDataset,
27+
)
2328
from OTAnalytics.domain.flow import Flow, FlowRepository
2429
from OTAnalytics.domain.section import Section, SectionId
30+
from OTAnalytics.domain.track_dataset.track_dataset import TrackIdSet, TrackIdSetFactory
2531
from OTAnalytics.domain.types import EventType
2632

2733
DATETIME_FORMAT = "%Y-%m-%d_%H-%M-%S"
@@ -598,16 +604,23 @@ class RoadUserAssignments:
598604
"""
599605

600606
@property
601-
def road_user_ids(self) -> list[str]:
607+
def road_user_ids(self) -> TrackIdSet:
602608
"""Returns a sorted list of all road user ids within this group of assignments.
603609
604610
Returns:
605611
list[str]: the road user ids.
606612
"""
607-
return sorted([assignment.road_user for assignment in self._assignments])
613+
return self._track_id_set_factory.create(
614+
{assignment.road_user for assignment in self._assignments}
615+
)
608616

609-
def __init__(self, assignments: list[RoadUserAssignment]) -> None:
617+
def __init__(
618+
self,
619+
assignments: list[RoadUserAssignment],
620+
track_id_set_factory: TrackIdSetFactory,
621+
) -> None:
610622
self._assignments = assignments.copy()
623+
self._track_id_set_factory = track_id_set_factory
611624

612625
def tag(self, by: Tagger) -> TaggedAssignments:
613626
"""
@@ -688,9 +701,9 @@ class FilterBySectionEnterEvent(RoadUserAssignerDecorator):
688701
"""Decorator to filters events by event type section-enter."""
689702

690703
def assign(self, events: Iterable[Event], flows: list[Flow]) -> RoadUserAssignments:
691-
section_enter_events: list[Event] = [
692-
event for event in events if event.event_type == EventType.SECTION_ENTER
693-
]
704+
section_enter_events = PythonEventDataset(
705+
[event for event in events if event.event_type == EventType.SECTION_ENTER]
706+
)
694707
return super().assign(section_enter_events, flows)
695708

696709

@@ -700,7 +713,9 @@ class SimpleRoadUserAssigner(RoadUserAssigner):
700713
"""
701714

702715
def __init__(
703-
self, flow_selection: FlowSelection = MaxDurationFlowSelection()
716+
self,
717+
track_id_set_factory: TrackIdSetFactory,
718+
flow_selection: FlowSelection = MaxDurationFlowSelection(),
704719
) -> None:
705720
"""
706721
Initialize the SimpleRoadUserAssigner with a flow selection strategy.
@@ -710,6 +725,7 @@ def __init__(
710725
Defaults to MaxDurationFlowSelection.
711726
"""
712727
self._flow_selection = flow_selection
728+
self._track_id_set_factory = track_id_set_factory
713729

714730
def assign(self, events: Iterable[Event], flows: list[Flow]) -> RoadUserAssignments:
715731
"""
@@ -748,18 +764,18 @@ def __group_flows_by_sections(
748764

749765
def __group_events_by_road_user(
750766
self, events: Iterable[Event]
751-
) -> dict[tuple[str, str], list[Event]]:
767+
) -> dict[tuple[str, str], EventDataset]:
752768
"""
753769
Group events by road user.
754770
755771
Args:
756772
events (Iterable[Event]): events of a road user
757773
758774
Returns:
759-
dict[tuple[RoadUserId, RoadUserType], list[Event]]: events grouped by user
775+
dict[tuple[RoadUserId, RoadUserType], EventDataset]: events grouped by user
760776
"""
761-
events_by_road_user: dict[tuple[RoadUserId, RoadUserType], list[Event]] = (
762-
defaultdict(list)
777+
events_by_road_user: dict[tuple[RoadUserId, RoadUserType], EventDataset] = (
778+
defaultdict(PythonEventDataset)
763779
)
764780
sorted_events = sorted(
765781
events, key=lambda _event: _event.interpolated_occurrence
@@ -774,15 +790,15 @@ def __group_events_by_road_user(
774790
def __assign_user_to_flow(
775791
self,
776792
flows: dict[tuple[SectionId, SectionId], list[Flow]],
777-
events_by_road_user: dict[tuple[str, str], list[Event]],
793+
events_by_road_user: dict[tuple[str, str], EventDataset],
778794
) -> RoadUserAssignments:
779795
"""
780796
Assign each user to flows based on the flow selection strategy.
781797
782798
Args:
783799
flows (dict[tuple[SectionId, SectionId], list[Flow]]): flows by start and
784800
end section
785-
events_by_road_user (dict[str, list[Event]]): events by road user
801+
events_by_road_user (dict[str, EventDataset]): events by road user
786802
787803
Returns:
788804
RoadUserAssignments: group of RoadUserAssignment objects
@@ -795,35 +811,35 @@ def __assign_user_to_flow(
795811
road_user_id=road_user_id, road_user_type=road_user_type
796812
)
797813
assignments.extend(user_assignments)
798-
return RoadUserAssignments(assignments)
814+
return RoadUserAssignments(assignments, self._track_id_set_factory)
799815

800816
def __create_candidates(
801817
self,
802818
flows: dict[tuple[SectionId, SectionId], list[Flow]],
803-
events: list[Event],
819+
events: EventDataset,
804820
) -> list[FlowCandidate]:
805821
"""
806822
Create flow candidates to select one from in a later step.
807823
808824
Args:
809825
flows (dict[tuple[SectionId, SectionId], list[Flow]]): flows by start and
810826
end section
811-
events (list[Event]): events belonging to road user
827+
events (EventDataset): events belonging to road user
812828
813829
Returns:
814830
list[FlowCandidate]: the flow candidates pertaining to road user
815831
"""
816832
event_pairs = self.__create_event_pairs(events)
817833
return self.__create_candidate_flows(flows, event_pairs)
818834

819-
def __create_event_pairs(self, events: list[Event]) -> list[EventPair]:
835+
def __create_event_pairs(self, events: EventDataset) -> list[EventPair]:
820836
"""
821837
Create event pairs.
822838
823839
Requires and assumes events to be sorted by occurrence.
824840
825841
Args:
826-
events(list[Event]): events to create the event pairs with
842+
events(EventDataset): events to create the event pairs with
827843
828844
Returns:
829845
list[EventPair]: event pairs

‎OTAnalytics/application/application.py‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@
8383
SectionId,
8484
SectionListObserver,
8585
)
86-
from OTAnalytics.domain.track import TrackId, TrackImage
8786
from OTAnalytics.domain.types import EventType
8887
from OTAnalytics.domain.video import Video, VideoListObserver
8988

@@ -415,19 +414,6 @@ def save_otflow(self, file: Path) -> None:
415414
"""
416415
self._save_otflow.save(file)
417416

418-
def get_image_of_track(self, track_id: TrackId) -> Optional[TrackImage]:
419-
"""
420-
Retrieve an image for the given track.
421-
422-
Args:
423-
track_id (TrackId): identifier for the track
424-
425-
Returns:
426-
Optional[TrackImage]: an image of the track if the track is available and
427-
the image can be loaded
428-
"""
429-
return self._datastore.get_image_of_track(track_id)
430-
431417
def create_events(self) -> None:
432418
"""
433419
Intersect all tracks with all sections and write the events into the event

0 commit comments

Comments
 (0)