Skip to content

Commit dddfa3f

Browse files
authored
Merge pull request #791 from OpenTrafficCam/feature/8954-change-strategy-to-parse-ottrk-files-by-files-in-streamottrkparser-2
Feature/8954 change strategy to parse ottrk files by files in streamottrkparser 2
2 parents 911de3d + 6c16ccd commit dddfa3f

26 files changed

Lines changed: 585 additions & 563 deletions

OTAnalytics/application/parser/cli_parser.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ class CliParseError(Exception):
3333
class CliArguments:
3434
start_cli: bool
3535
cli_mode: CliMode
36-
cli_chunk_size: int
3736
debug: bool
3837
logfile_overwrite: bool
3938
track_export: bool

OTAnalytics/application/run_configuration.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,6 @@ def cli_bulk_mode(self) -> bool:
7070
def cli_stream_mode(self) -> bool:
7171
return self.start_cli and self._cli_args.cli_mode == CliMode.STREAM
7272

73-
@property
74-
def cli_chunk_size(self) -> int:
75-
return self._cli_args.cli_chunk_size
76-
7773
@property
7874
def debug(self) -> bool:
7975
return self._cli_args.debug

OTAnalytics/domain/track.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ def occurrence(self) -> datetime:
9494
def interpolated_detection(self) -> bool:
9595
raise NotImplementedError
9696

97+
@property
98+
@abstractmethod
99+
def is_finished(self) -> bool:
100+
raise NotImplementedError
101+
97102
@property
98103
@abstractmethod
99104
def track_id(self) -> TrackId:

OTAnalytics/domain/track_dataset/filtered_track_dataset.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ def get_last_segments(self) -> TrackSegmentDataset:
7777
def get_max_confidences_for(self, track_ids: TrackIdSet) -> dict[str, float]:
7878
return self._filter().get_max_confidences_for(track_ids)
7979

80+
def split_finished(self) -> tuple[TrackDataset, TrackDataset]:
81+
return self._filter().split_finished()
82+
8083

8184
class FilterByClassTrackDataset(FilteredTrackDataset):
8285
@property

OTAnalytics/domain/track_dataset/track_dataset.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,16 @@ def clear(self) -> "TrackDataset":
212212
"""
213213
raise NotImplementedError
214214

215+
@abstractmethod
216+
def split_finished(self) -> tuple["TrackDataset", "TrackDataset"]:
217+
"""
218+
Split this dataset into finished and remaining tracks.
219+
220+
Returns:
221+
tuple[TrackDataset, TrackDataset]: (finished_tracks, remaining_tracks)
222+
"""
223+
raise NotImplementedError
224+
215225
@abstractmethod
216226
def as_list(self) -> list[Track]:
217227
raise NotImplementedError

OTAnalytics/plugin_datastore/polars_track_store.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ def occurrence(self) -> datetime:
106106
def interpolated_detection(self) -> bool:
107107
return self.__get_attribute(track.INTERPOLATED_DETECTION)
108108

109+
@property
110+
def is_finished(self) -> bool:
111+
if ottrk_dataformat.FINISHED not in self._data:
112+
return False
113+
return bool(self.__get_attribute(ottrk_dataformat.FINISHED))
114+
109115
@property
110116
def track_id(self) -> TrackId:
111117
return TrackId(self._track_id)
@@ -445,6 +451,36 @@ def get_for(self, id: TrackId) -> Optional[Track]:
445451
def clear(self) -> "PolarsTrackDataset":
446452
return PolarsTrackDataset(self.track_geometry_factory)
447453

454+
def split_finished(self) -> tuple[TrackDataset, TrackDataset]:
455+
empty = PolarsTrackDataset(
456+
self.track_geometry_factory, calculator=self.calculator
457+
)
458+
if self._dataset.is_empty():
459+
return empty, empty
460+
if ottrk_dataformat.FINISHED not in self._dataset.columns:
461+
return self, empty
462+
463+
finished_ids = (
464+
self._dataset.filter(
465+
pl.col(ottrk_dataformat.FINISHED).fill_null(False).eq(True)
466+
)
467+
.get_column(LEVEL_TRACK_ID)
468+
.unique()
469+
.to_list()
470+
)
471+
if not finished_ids:
472+
return empty, self
473+
474+
all_ids = self._dataset.get_column(LEVEL_TRACK_ID).unique().to_list()
475+
finished_id_set = set(finished_ids)
476+
remaining_ids = [
477+
track_id for track_id in all_ids if track_id not in finished_id_set
478+
]
479+
480+
finished_dataset = self._subset_by_ids(finished_ids)
481+
remaining_dataset = self._subset_by_ids(remaining_ids)
482+
return finished_dataset, remaining_dataset
483+
448484
def _create_track_flyweight(self, track_id: str) -> Track:
449485
"""Create a Track flyweight object for the given track_id."""
450486
track_data = self._dataset.filter(pl.col(LEVEL_TRACK_ID) == track_id)
@@ -830,6 +866,20 @@ def split(self, batches: int) -> Sequence["PolarsTrackDataset"]:
830866
)
831867
return new_batches
832868

869+
def _subset_by_ids(self, track_ids: list[str]) -> "PolarsTrackDataset":
870+
if not track_ids:
871+
return PolarsTrackDataset(
872+
self.track_geometry_factory, calculator=self.calculator
873+
)
874+
subset = self._dataset.filter(pl.col(LEVEL_TRACK_ID).is_in(track_ids))
875+
geometries = self._get_geometries_for(track_ids)
876+
return PolarsTrackDataset.from_dataframe(
877+
subset,
878+
self.track_geometry_factory,
879+
geometries,
880+
calculator=self.calculator,
881+
)
882+
833883
def _get_geometries_for(
834884
self, track_ids: list[str]
835885
) -> dict[RelativeOffsetCoordinate, PolarsTrackGeometryDataset]:
@@ -885,6 +935,8 @@ def create_empty_dataframe() -> pl.DataFrame:
885935
schema[track.FRAME] = pl.Int64
886936
schema[track.CONFIDENCE] = pl.Float64
887937
schema[track.INTERPOLATED_DETECTION] = pl.Boolean
938+
schema[ottrk_dataformat.FIRST] = pl.Boolean
939+
schema[ottrk_dataformat.FINISHED] = pl.Boolean
888940
schema[track.OCCURRENCE] = pl.Datetime
889941

890942
return pl.DataFrame(schema=schema)
@@ -909,7 +961,7 @@ def _convert_tracks(tracks: Iterable[Track]) -> pl.DataFrame:
909961
dto = detection.to_dict()
910962
dto[track.ORIGINAL_TRACK_ID] = current_track.original_id.id
911963
dto[ottrk_dataformat.FIRST] = current_track.first_detection == detection
912-
dto[ottrk_dataformat.FINISHED] = current_track.last_detection == detection
964+
dto[ottrk_dataformat.FINISHED] = detection.is_finished
913965
prepared.append(dto)
914966

915967
if not prepared:

OTAnalytics/plugin_datastore/python_track_store.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class PythonDetection(Detection, DataclassValidation):
9393
_track_id: TrackId
9494
_video_name: str
9595
_input_file: str
96+
_is_finished: bool = False
9697

9798
@property
9899
def classification(self) -> str:
@@ -130,6 +131,10 @@ def occurrence(self) -> datetime:
130131
def interpolated_detection(self) -> bool:
131132
return self._interpolated_detection
132133

134+
@property
135+
def is_finished(self) -> bool:
136+
return self._is_finished
137+
133138
@property
134139
def track_id(self) -> TrackId:
135140
return self._track_id
@@ -541,6 +546,43 @@ def _remove_from_geometry_datasets(
541546
def clear(self) -> "PythonTrackDataset":
542547
return PythonTrackDataset(self.track_geometry_factory)
543548

549+
def split_finished(self) -> tuple[TrackDataset, TrackDataset]:
550+
empty = PythonTrackDataset(
551+
self.track_geometry_factory, calculator=self.calculator
552+
)
553+
if not self._tracks:
554+
return empty, empty
555+
556+
finished_tracks: dict[TrackId, Track] = {}
557+
remaining_tracks: dict[TrackId, Track] = {}
558+
for track_id, track in self._tracks.items():
559+
if track.last_detection.is_finished:
560+
finished_tracks[track_id] = track
561+
else:
562+
remaining_tracks[track_id] = track
563+
564+
finished_dataset = (
565+
PythonTrackDataset(
566+
self.track_geometry_factory,
567+
finished_tracks,
568+
self._get_geometries_for(finished_tracks.keys()),
569+
calculator=self.calculator,
570+
)
571+
if finished_tracks
572+
else empty
573+
)
574+
remaining_dataset = (
575+
PythonTrackDataset(
576+
self.track_geometry_factory,
577+
remaining_tracks,
578+
self._get_geometries_for(remaining_tracks.keys()),
579+
calculator=self.calculator,
580+
)
581+
if remaining_tracks
582+
else empty
583+
)
584+
return finished_dataset, remaining_dataset
585+
544586
def as_list(self) -> list[Track]:
545587
return list(self._tracks.values())
546588

OTAnalytics/plugin_datastore/track_store.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ def occurrence(self) -> datetime:
9494
def interpolated_detection(self) -> bool:
9595
return self.__get_attribute(track.INTERPOLATED_DETECTION)
9696

97+
@property
98+
def is_finished(self) -> bool:
99+
if ottrk_dataformat.FINISHED not in self._data.index:
100+
return False
101+
value = self.__get_attribute(ottrk_dataformat.FINISHED)
102+
return bool(value)
103+
97104
@property
98105
def track_id(self) -> TrackId:
99106
return TrackId(self._track_id)
@@ -403,6 +410,38 @@ def get_for(self, id: TrackId) -> Optional[Track]:
403410
def clear(self) -> "PandasTrackDataset":
404411
return PandasTrackDataset(self.track_geometry_factory)
405412

413+
def split_finished(self) -> tuple[TrackDataset, TrackDataset]:
414+
empty = PandasTrackDataset(
415+
self.track_geometry_factory, calculator=self.calculator
416+
)
417+
if self._dataset.empty:
418+
return empty, empty
419+
if ottrk_dataformat.FINISHED not in self._dataset.columns:
420+
return self, empty
421+
422+
finished_mask = (
423+
self._dataset[ottrk_dataformat.FINISHED].fillna(False).astype(bool).eq(True)
424+
)
425+
finished_ids = (
426+
self._dataset.loc[finished_mask]
427+
.index.get_level_values(LEVEL_TRACK_ID)
428+
.unique()
429+
)
430+
finished_ids_list = list(finished_ids)
431+
if not finished_ids_list:
432+
return empty, self
433+
434+
index = self.get_index()
435+
all_ids = list(index) if index is not None else []
436+
finished_id_set = set(finished_ids_list)
437+
remaining_ids_list = [
438+
track_id for track_id in all_ids if track_id not in finished_id_set
439+
]
440+
441+
finished_dataset = self._subset_by_ids(finished_ids_list)
442+
remaining_dataset = self._subset_by_ids(remaining_ids_list)
443+
return finished_dataset, remaining_dataset
444+
406445
def remove(self, track_id: TrackId) -> "PandasTrackDataset":
407446
remaining_tracks = self._dataset.drop(unpack(track_id), errors="ignore")
408447
updated_geometry_datasets = self._remove_from_geometry_dataset([track_id.id])
@@ -471,6 +510,20 @@ def split(self, batches: int) -> Sequence["PandasTrackDataset"]:
471510

472511
return [self]
473512

513+
def _subset_by_ids(self, track_ids: list[str]) -> "PandasTrackDataset":
514+
if not track_ids:
515+
return PandasTrackDataset(
516+
self.track_geometry_factory, calculator=self.calculator
517+
)
518+
subset = self._dataset.loc[track_ids]
519+
geometries = self._get_geometries_for(track_ids)
520+
return PandasTrackDataset.from_dataframe(
521+
subset,
522+
self.track_geometry_factory,
523+
geometries,
524+
calculator=self.calculator,
525+
)
526+
474527
def get_index(self) -> Index | None:
475528
if self._dataset.empty:
476529
return None
@@ -802,6 +855,8 @@ def _convert_tracks(tracks: Iterable[Track]) -> DataFrame:
802855
for detection in current_track.detections:
803856
dto = detection.to_dict()
804857
dto[track.ORIGINAL_TRACK_ID] = current_track.original_id.id
858+
dto[ottrk_dataformat.FIRST] = current_track.first_detection == detection
859+
dto[ottrk_dataformat.FINISHED] = detection.is_finished
805860
prepared.append(dto)
806861

807862
if not prepared:

OTAnalytics/plugin_parser/argparse_cli_parser.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,6 @@ def _setup(self) -> None:
4343
default=CliMode.BULK,
4444
required=False,
4545
)
46-
self._parser.add_argument(
47-
"--cli-chunk-size",
48-
type=int,
49-
help="Specify the chunk size for streaming OTAnalytics CLI.",
50-
default=10,
51-
required=False,
52-
)
5346
self._parser.add_argument(
5447
"--show-svz",
5548
action="store_true",
@@ -176,7 +169,6 @@ def parse(self) -> CliArguments:
176169
start_cli=args.cli,
177170
start_webui=args.webui,
178171
cli_mode=args.cli_mode,
179-
cli_chunk_size=args.cli_chunk_size,
180172
debug=args.debug,
181173
logfile_overwrite=args.logfile_overwrite,
182174
show_svz=args.show_svz,

OTAnalytics/plugin_parser/otvision_parser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ def parse_python_detection(
440440
_video_name=metadata_video[ottrk_format.FILENAME]
441441
+ metadata_video[ottrk_format.FILETYPE],
442442
_input_file=input_file,
443+
_is_finished=det_dict.get(ottrk_format.FINISHED, False),
443444
)
444445

445446

0 commit comments

Comments
 (0)