Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
418e911
Add intersection of track segments with a single line
briemla May 14, 2025
831208d
Add test cases
briemla May 14, 2025
854b7fd
Introduce constants
briemla May 15, 2025
13915c0
Use constants in tests
briemla May 15, 2025
016be45
Separate functionality
briemla May 15, 2025
d766152
Intersect a polygon
briemla May 15, 2025
aaf8f3e
Use constant
briemla May 16, 2025
4c5e17d
Move polygon
briemla May 16, 2025
8ff8b27
Implement intersection with first line segment of section
briemla Jun 26, 2025
d19b5e1
Process all legs of a segment
briemla Jun 26, 2025
86e9cc7
Fix type cast
briemla Aug 13, 2025
e7a2b29
Ensure offset is used
briemla Aug 13, 2025
44e7e6c
Add pure pandas and 2h test data
briemla Aug 13, 2025
0b9b88d
Add 4h benchmark
briemla Aug 13, 2025
cc530d8
Extend benchmark to check pure pandas implementation
briemla Aug 14, 2025
15ee577
Optimize calculation
briemla Aug 14, 2025
b594181
Add offset
briemla Aug 14, 2025
706357b
Fix missing offset in intersection coordinate calculation
briemla Aug 14, 2025
aa3bc49
Update todo comments
briemla Aug 14, 2025
16f1255
Fix different track id classes and add further todos
briemla Aug 14, 2025
b52bc2e
Use pandas geometry dataset
briemla Aug 14, 2025
9cba860
Remove shapely geometry store in base_application
briemla Aug 14, 2025
a719ad5
Implement add_all, remove and get_for
briemla Aug 14, 2025
476f2ad
Fix broken test
briemla Aug 14, 2025
4bacd4f
Clean up unused method
briemla Aug 14, 2025
f027d5d
Add script to convert ottrk to feather format
briemla Aug 15, 2025
078f2a4
Directly use pandas version
briemla Aug 15, 2025
18cc478
Implement feather track parser
briemla Aug 15, 2025
fb0cf9e
Use feathers parser
briemla Aug 15, 2025
0793c73
Convert a bunch of files to feathers
briemla Aug 15, 2025
88d296a
Allow TrackParser implementations to parse multiple files more optimized
briemla Aug 15, 2025
697e600
Parse multiple files at once in FeathersParser
briemla Aug 15, 2025
3b6ece1
Use multi file parsing when loading ottrk files
briemla Aug 15, 2025
0b1a7f6
Remove unnecessary code
briemla Aug 15, 2025
2a5191f
Remove unnecessary code
briemla Aug 15, 2025
111e4e1
Remove unnecessary code
briemla Aug 15, 2025
db673f0
Extract convertion code into plugin
briemla Aug 16, 2025
45b6188
Automatically convert files from ottrk to feathers format
briemla Aug 16, 2025
2bd0aaf
Add polars datasets
briemla Aug 20, 2025
b96d811
Implement contains method
briemla Aug 20, 2025
29426ef
Use efficient conversion from polars to pandas
briemla Aug 20, 2025
f025d6f
Simplify shown video
briemla Aug 20, 2025
d9b1208
Transfer from unittest to pytest and use polars instead of pandas parser
briemla Aug 20, 2025
2965f3c
Move calculation of ids inside section into dataset
briemla Aug 20, 2025
eaedc36
Fix different datatypes of ids
briemla Aug 20, 2025
21e7a45
Use polars track dataset with simplified cutting
briemla Aug 20, 2025
2fe3146
Fix track id datatypes
briemla Aug 20, 2025
a2e5e17
Set tracks after initial cutting
briemla Aug 20, 2025
9a418a8
Fix track ids after cutting
briemla Aug 20, 2025
5bc8a9a
Add TrackIdSet to efficiently handle TrackIds
briemla Aug 27, 2025
f30e092
WORK IN PROGRESS: Use TrackIdSet where possible
briemla Aug 29, 2025
5da2e42
Use TrackIdSet where possible
briemla Aug 30, 2025
84af6a6
Use TrackIdSet to remove multiple tracks at once
briemla Aug 30, 2025
c98d540
Use TrackIdSet methods to speed up track statistics
briemla Sep 2, 2025
aff4371
Remove unused method
briemla Sep 17, 2025
5cee394
Fix broken test
briemla Sep 18, 2025
e683f4d
Encapsulate resulting intersection points
briemla Sep 18, 2025
f69e727
Move calculation of events into domain class to improve performance w…
briemla Sep 18, 2025
64762de
Introduce EventDataset to encapsulate events.
briemla Sep 18, 2025
53d874e
Use EventDataset instead of bare list
briemla Sep 18, 2025
7c6f080
Remove unused code
briemla Sep 18, 2025
07cd29e
Raise exception
briemla Sep 18, 2025
a4a501c
Change internal type
briemla Sep 18, 2025
56d18b7
Remove unused code
briemla Sep 18, 2025
8b8a4f3
Create abstract base for event data
briemla Sep 18, 2025
9fd65e6
Merge origin/main into feature branch customer-reqs/8596-speed-up-ota…
briemla Sep 23, 2025
66319c1
Merge origin/main into feature branch customer-reqs/8596-speed-up-ota…
briemla Sep 23, 2025
d990731
Fix failed test after merge
briemla Sep 23, 2025
0d238ce
Remove unused import
briemla Sep 23, 2025
d86b89f
Move log output
briemla Sep 24, 2025
f0a21a8
Extract IntersectionPointsDataset interface
briemla Sep 24, 2025
3f81f32
Implement polars version of IntersectionPointsDataset
briemla Sep 24, 2025
0a94240
Use polars geometry set
briemla Sep 24, 2025
5ead350
Convert pandas test to polars test
briemla Sep 24, 2025
b5c1780
Remove unused pandas implementation
briemla Sep 24, 2025
5282b87
Remove clean architecture violation by introducing a TrackIdSetFactory
briemla Sep 24, 2025
c8f324c
Increase chunk size to speed up regression tests
briemla Sep 24, 2025
8cefbf2
Fix clean architecture violation
briemla Sep 24, 2025
9a4d8cd
Fix clean architecture violation by using a factory
briemla Sep 24, 2025
002c8ef
Fix clean architecture violation by using TrackIdSet instead of iterable
briemla Sep 24, 2025
361ba4c
Fix clean architecture violation by using TrackIdSet instead of iterable
briemla Sep 24, 2025
48973e1
Extract classes into separate file
briemla Sep 24, 2025
fabdccd
Extract test code
briemla Sep 24, 2025
bcc5bb8
Add filtered versions of TrackDataset for polars
briemla Sep 24, 2025
4ddd992
Add script to check clean architecture violations
briemla Sep 24, 2025
51c2b05
Fix imports
briemla Sep 24, 2025
ba1ceb9
Activate filter classes using polars
briemla Sep 24, 2025
f161c22
Remove unused wrapped method
briemla Sep 24, 2025
94389fe
Renamed wrapped method name
briemla Sep 24, 2025
8835027
Extract constants
briemla Sep 24, 2025
334b222
Fix version in precommit
briemla Sep 24, 2025
0d96b57
Fix version in precommit
briemla Sep 24, 2025
b0432d4
Clean up benchmark
briemla Sep 24, 2025
f8b7ecc
Implement missing method
briemla Sep 25, 2025
5d871dd
Add documentation
briemla Sep 25, 2025
f9ce9ee
Replace `print` statements with `logger` for consistent logging and u…
randy-seng Oct 2, 2025
227678e
Refactor: Introduce constants for file extensions and update referenc…
randy-seng Oct 2, 2025
77fd2aa
Add TODO comments to check and potentially remove unimplemented methods
randy-seng Oct 2, 2025
f1ef2bc
Remove duplicate import in `test_inside_cutting_section` test.
randy-seng Oct 2, 2025
3026172
Add assertions for `update_detection_classes` calls in `TestLoadTrack…
randy-seng Oct 2, 2025
b236979
Reintroduce removed types for mypy.
briemla Oct 6, 2025
6651de5
Merge remote-tracking branch 'origin/customer-reqs/8596-speed-up-otan…
briemla Oct 6, 2025
be93ae7
Sort ids
briemla Oct 6, 2025
c2c7955
Rename method
briemla Oct 6, 2025
e9b56a6
Remove unnecessary local import
briemla Oct 6, 2025
d8ba935
Return old track ids when cutting
briemla Oct 6, 2025
a088489
Use unpack where possible
briemla Oct 6, 2025
58e9a83
Use unpack where possible
briemla Oct 6, 2025
6bba8a6
Remove unnecessary conversion
briemla Oct 6, 2025
a9ea720
Change calculation of intersection parameters to follow the normally …
briemla Oct 6, 2025
ddfc106
Fix assertions in regression tests
briemla Oct 6, 2025
ab9a6b1
Simplify file loading in assertions
briemla Oct 6, 2025
526e5ff
Fix intersection calculation
briemla Oct 8, 2025
c4196dd
Refactor: Introduce constants for metadata keys and update references…
randy-seng Oct 2, 2025
67804c6
Refactor: Introduce constants for recorded start date in test metadat…
randy-seng Oct 2, 2025
e3191c3
Refactor: Streamline unpacking track IDs by using set comprehension f…
randy-seng Oct 2, 2025
e50b2fe
Remove redundant import `EmptyTrackIdSet` from `polars_track_store.py`.
randy-seng Oct 3, 2025
00476f3
Fix intersection calculation
randy-seng Oct 11, 2025
870d10d
Fix direction vector calculation in PolarsGeometryDataset
randy-seng Oct 13, 2025
cf67bba
Add interpolated columns to output
briemla Oct 14, 2025
bc36368
Fix wrong columns and extract hostname
briemla Oct 14, 2025
86590eb
Fix missing override when using filterd track dataset
briemla Oct 15, 2025
fed50c6
Compare csv files using polars and differ between events and counts
briemla Oct 15, 2025
72eed3f
Fix track id for not cut tracks
briemla Oct 15, 2025
03f2d1f
Merge branch 'main' into customer-reqs/8596-speed-up-otanalytics-by-u…
briemla Oct 23, 2025
df148db
Merge remote-tracking branch 'origin/customer-reqs/8596-speed-up-otan…
briemla Oct 23, 2025
e7ebc4d
OP#8596:
briemla Oct 24, 2025
8cb25c0
Merge remote-tracking branch 'refs/remotes/origin/bug/8708-regression…
briemla Oct 27, 2025
6927543
Merge branch 'main' into customer-reqs/8596-speed-up-otanalytics-by-u…
randy-seng Oct 27, 2025
8100e71
OP#8596:
briemla Oct 27, 2025
1b26c44
OP#8596:
briemla Oct 27, 2025
fc7f249
OP#8596:
briemla Oct 27, 2025
3503baf
OP#8596:
briemla Oct 27, 2025
83d42b0
Merge remote-tracking branch 'origin/customer-reqs/8596-speed-up-otan…
briemla Oct 27, 2025
159c477
Merge branch 'main' into customer-reqs/8596-speed-up-otanalytics-by-u…
randy-seng Oct 27, 2025
ef009d3
OP#8596: Sort intersected series after filtering in `polars_track_id_…
randy-seng Oct 27, 2025
81aae18
Merge branch 'main' into customer-reqs/8596-speed-up-otanalytics-by-u…
randy-seng Oct 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,30 @@ repos:
entry: mypy OTAnalytics tests --config-file=pyproject.toml
additional_dependencies:
- av==13.0.0
- black==25.1.0
- black==24.8.0
- customtkinter==5.2.2
- fastapi==0.115.13
- fastapi==0.116.2
- hatch-requirements-txt==0.4.1
- ijson==3.3.0
- interrogate==1.7.0
- isort==6.0.1
- isort==5.13.2
- matplotlib==3.10.0
- more-itertools==10.5.0
- more-itertools==10.7.0
- mypy==1.16.1
- nicegui==2.20.0
- nicegui==2.24.1
- numpy==2.1.1
- opencv-python-headless==4.10.0.84
Comment thread
briemla marked this conversation as resolved.
- pandas-stubs==2.2.3.241126
- pandas==2.2.3
- polars==1.32.3
- pre-commit==4.1.0
- pyarrow==21.0.0
- pytest-asyncio==0.24.0
- pytest-benchmark==5.1.0
- pytest-cov==6.0.0
- pytest-lazy-fixtures==1.1.2
- pytest==8.3.4
- starlette==0.45.3
- starlette==0.48.0
- twine==6.1.0
- types-PyYAML
- types-flake8
Expand Down
2 changes: 2 additions & 0 deletions OTAnalytics/adapter_ui/dummy_viewmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,7 @@ async def remove_flows(self) -> None:
)

def create_events(self) -> None:
logger().info("Creating events")
start_msg_popup = self._ui_factory.minimal_info_box(
message="Create events...",
initial_position=self.get_position(),
Expand All @@ -1343,6 +1344,7 @@ def create_events(self) -> None:
start_msg_popup.update_message(message="Creating events completed")
sleep(1)
start_msg_popup.close()
logger().info("Events created")

def get_all_flow_ids(self) -> list[FlowId]:
return [flow.id for flow in self.get_all_flows()]
Expand Down
12 changes: 4 additions & 8 deletions OTAnalytics/application/analysis/intersect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,27 @@
from collections import defaultdict
from typing import Iterable, Mapping

from OTAnalytics.domain.event import Event
from OTAnalytics.domain.event import EventDataset
from OTAnalytics.domain.geometry import RelativeOffsetCoordinate
from OTAnalytics.domain.section import Section, SectionId
from OTAnalytics.domain.track import TrackId
from OTAnalytics.domain.track_dataset.track_dataset import TrackIdSet
from OTAnalytics.domain.types import EventType


class IntersectionError(Exception):
pass


class RunIntersect(ABC):
"""
Interface defining the use case to intersect the given tracks with the given
sections.
"""

@abstractmethod
def __call__(self, sections: Iterable[Section]) -> list[Event]:
def __call__(self, sections: Iterable[Section]) -> EventDataset:
raise NotImplementedError


class TracksIntersectingSections(ABC):
@abstractmethod
def __call__(self, sections: Iterable[Section]) -> dict[SectionId, set[TrackId]]:
def __call__(self, sections: Iterable[Section]) -> dict[SectionId, TrackIdSet]:
raise NotImplementedError


Expand Down
54 changes: 35 additions & 19 deletions OTAnalytics/application/analysis/traffic_counting.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
from OTAnalytics.application.export_formats.export_mode import ExportMode
from OTAnalytics.application.use_cases.create_events import CreateEvents
from OTAnalytics.application.use_cases.section_repository import GetSectionsById
from OTAnalytics.domain.event import Event, EventRepository
from OTAnalytics.domain.event import (
Event,
EventDataset,
EventRepository,
PythonEventDataset,
)
from OTAnalytics.domain.flow import Flow, FlowRepository
from OTAnalytics.domain.section import Section, SectionId
from OTAnalytics.domain.track_dataset.track_dataset import TrackIdSet, TrackIdSetFactory
from OTAnalytics.domain.types import EventType

DATETIME_FORMAT = "%Y-%m-%d_%H-%M-%S"
Expand Down Expand Up @@ -598,16 +604,23 @@ class RoadUserAssignments:
"""

@property
def road_user_ids(self) -> list[str]:
def road_user_ids(self) -> TrackIdSet:
"""Returns a sorted list of all road user ids within this group of assignments.

Returns:
list[str]: the road user ids.
"""
return sorted([assignment.road_user for assignment in self._assignments])
return self._track_id_set_factory.create(
{assignment.road_user for assignment in self._assignments}
)

def __init__(self, assignments: list[RoadUserAssignment]) -> None:
def __init__(
self,
assignments: list[RoadUserAssignment],
track_id_set_factory: TrackIdSetFactory,
) -> None:
self._assignments = assignments.copy()
self._track_id_set_factory = track_id_set_factory

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

def assign(self, events: Iterable[Event], flows: list[Flow]) -> RoadUserAssignments:
section_enter_events: list[Event] = [
event for event in events if event.event_type == EventType.SECTION_ENTER
]
section_enter_events = PythonEventDataset(
[event for event in events if event.event_type == EventType.SECTION_ENTER]
)
return super().assign(section_enter_events, flows)


Expand All @@ -700,7 +713,9 @@ class SimpleRoadUserAssigner(RoadUserAssigner):
"""

def __init__(
self, flow_selection: FlowSelection = MaxDurationFlowSelection()
self,
track_id_set_factory: TrackIdSetFactory,
flow_selection: FlowSelection = MaxDurationFlowSelection(),
) -> None:
"""
Initialize the SimpleRoadUserAssigner with a flow selection strategy.
Expand All @@ -710,6 +725,7 @@ def __init__(
Defaults to MaxDurationFlowSelection.
"""
self._flow_selection = flow_selection
self._track_id_set_factory = track_id_set_factory

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

def __group_events_by_road_user(
self, events: Iterable[Event]
) -> dict[tuple[str, str], list[Event]]:
) -> dict[tuple[str, str], EventDataset]:
"""
Group events by road user.

Args:
events (Iterable[Event]): events of a road user

Returns:
dict[tuple[RoadUserId, RoadUserType], list[Event]]: events grouped by user
dict[tuple[RoadUserId, RoadUserType], EventDataset]: events grouped by user
"""
events_by_road_user: dict[tuple[RoadUserId, RoadUserType], list[Event]] = (
defaultdict(list)
events_by_road_user: dict[tuple[RoadUserId, RoadUserType], EventDataset] = (
defaultdict(PythonEventDataset)
)
sorted_events = sorted(
events, key=lambda _event: _event.interpolated_occurrence
Expand All @@ -774,15 +790,15 @@ def __group_events_by_road_user(
def __assign_user_to_flow(
self,
flows: dict[tuple[SectionId, SectionId], list[Flow]],
events_by_road_user: dict[tuple[str, str], list[Event]],
events_by_road_user: dict[tuple[str, str], EventDataset],
) -> RoadUserAssignments:
"""
Assign each user to flows based on the flow selection strategy.

Args:
flows (dict[tuple[SectionId, SectionId], list[Flow]]): flows by start and
end section
events_by_road_user (dict[str, list[Event]]): events by road user
events_by_road_user (dict[str, EventDataset]): events by road user

Returns:
RoadUserAssignments: group of RoadUserAssignment objects
Expand All @@ -795,35 +811,35 @@ def __assign_user_to_flow(
road_user_id=road_user_id, road_user_type=road_user_type
)
assignments.extend(user_assignments)
return RoadUserAssignments(assignments)
return RoadUserAssignments(assignments, self._track_id_set_factory)

def __create_candidates(
self,
flows: dict[tuple[SectionId, SectionId], list[Flow]],
events: list[Event],
events: EventDataset,
) -> list[FlowCandidate]:
"""
Create flow candidates to select one from in a later step.

Args:
flows (dict[tuple[SectionId, SectionId], list[Flow]]): flows by start and
end section
events (list[Event]): events belonging to road user
events (EventDataset): events belonging to road user

Returns:
list[FlowCandidate]: the flow candidates pertaining to road user
"""
event_pairs = self.__create_event_pairs(events)
return self.__create_candidate_flows(flows, event_pairs)

def __create_event_pairs(self, events: list[Event]) -> list[EventPair]:
def __create_event_pairs(self, events: EventDataset) -> list[EventPair]:
"""
Create event pairs.

Requires and assumes events to be sorted by occurrence.

Args:
events(list[Event]): events to create the event pairs with
events(EventDataset): events to create the event pairs with

Returns:
list[EventPair]: event pairs
Expand Down
14 changes: 0 additions & 14 deletions OTAnalytics/application/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@
SectionId,
SectionListObserver,
)
from OTAnalytics.domain.track import TrackId, TrackImage
from OTAnalytics.domain.types import EventType
from OTAnalytics.domain.video import Video, VideoListObserver

Expand Down Expand Up @@ -415,19 +414,6 @@ def save_otflow(self, file: Path) -> None:
"""
self._save_otflow.save(file)

def get_image_of_track(self, track_id: TrackId) -> Optional[TrackImage]:
"""
Retrieve an image for the given track.

Args:
track_id (TrackId): identifier for the track

Returns:
Optional[TrackImage]: an image of the track if the track is available and
the image can be loaded
"""
return self._datastore.get_image_of_track(track_id)

def create_events(self) -> None:
"""
Intersect all tracks with all sections and write the events into the event
Expand Down
Loading