@@ -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 :
0 commit comments