@@ -202,19 +202,26 @@ def clear_frames(self, frames: Container[int]):
202
202
def to_shapes (self ,
203
203
end_frame : int ,
204
204
* ,
205
- included_frames : Optional [Sequence [int ]] = None ,
205
+ deleted_frames : Sequence [int ] | None = None ,
206
+ included_frames : Sequence [int ] | None = None ,
206
207
include_outside : bool = False ,
207
- use_server_track_ids : bool = False
208
+ use_server_track_ids : bool = False ,
208
209
) -> list :
209
210
shapes = self .data .shapes
210
211
tracks = TrackManager (self .data .tracks , dimension = self .dimension )
211
212
212
213
if included_frames is not None :
213
214
shapes = [s for s in shapes if s ["frame" ] in included_frames ]
214
215
215
- return shapes + tracks .to_shapes (end_frame ,
216
- included_frames = included_frames , include_outside = include_outside ,
217
- use_server_track_ids = use_server_track_ids
216
+ if deleted_frames is not None :
217
+ shapes = [s for s in shapes if s ["frame" ] not in deleted_frames ]
218
+
219
+ return shapes + tracks .to_shapes (
220
+ end_frame ,
221
+ included_frames = included_frames ,
222
+ deleted_frames = deleted_frames ,
223
+ include_outside = include_outside ,
224
+ use_server_track_ids = use_server_track_ids ,
218
225
)
219
226
220
227
def to_tracks (self ):
@@ -462,10 +469,14 @@ def _modify_unmatched_object(self, obj, end_frame):
462
469
463
470
464
471
class TrackManager (ObjectManager ):
465
- def to_shapes (self , end_frame : int , * ,
466
- included_frames : Optional [Sequence [int ]] = None ,
472
+ def to_shapes (
473
+ self ,
474
+ end_frame : int ,
475
+ * ,
476
+ included_frames : Sequence [int ] | None = None ,
477
+ deleted_frames : Sequence [int ] | None = None ,
467
478
include_outside : bool = False ,
468
- use_server_track_ids : bool = False
479
+ use_server_track_ids : bool = False ,
469
480
) -> list :
470
481
shapes = []
471
482
for idx , track in enumerate (self .objects ):
@@ -479,6 +490,7 @@ def to_shapes(self, end_frame: int, *,
479
490
self .dimension ,
480
491
include_outside = include_outside ,
481
492
included_frames = included_frames ,
493
+ deleted_frames = deleted_frames ,
482
494
):
483
495
shape ["label_id" ] = track ["label_id" ]
484
496
shape ["group" ] = track ["group" ]
@@ -498,10 +510,12 @@ def to_shapes(self, end_frame: int, *,
498
510
element_included_frames = set (track_shapes .keys ())
499
511
if included_frames is not None :
500
512
element_included_frames = element_included_frames .intersection (included_frames )
501
- element_shapes = track_elements .to_shapes (end_frame ,
513
+ element_shapes = track_elements .to_shapes (
514
+ end_frame ,
502
515
included_frames = element_included_frames ,
516
+ deleted_frames = deleted_frames ,
503
517
include_outside = True , # elements are controlled by the parent shape
504
- use_server_track_ids = use_server_track_ids
518
+ use_server_track_ids = use_server_track_ids ,
505
519
)
506
520
507
521
for shape in element_shapes :
@@ -588,10 +602,24 @@ def _modify_unmatched_object(self, obj, end_frame):
588
602
589
603
@staticmethod
590
604
def get_interpolated_shapes (
591
- track , start_frame , end_frame , dimension , * ,
605
+ track : dict ,
606
+ start_frame : int ,
607
+ end_frame : int ,
608
+ dimension : DimensionType | str ,
609
+ * ,
592
610
included_frames : Optional [Sequence [int ]] = None ,
611
+ deleted_frames : Optional [Sequence [int ]] = None ,
593
612
include_outside : bool = False ,
594
613
):
614
+ # If a task or job contains deleted frames that contain track keyframes,
615
+ # these keyframes should be excluded from the interpolation.
616
+ # In jobs having specific frames included (e.g. GT jobs),
617
+ # deleted frames should not be confused with included frames during track interpolation.
618
+ # Deleted frames affect existing shapes in tracks.
619
+ # Included frames filter the resulting annotations after interpolation
620
+ # to produce the requested track frames.
621
+ deleted_frames = deleted_frames or []
622
+
595
623
def copy_shape (source , frame , points = None , rotation = None ):
596
624
copied = source .copy ()
597
625
copied ["attributes" ] = faster_deepcopy (source ["attributes" ])
@@ -930,7 +958,7 @@ def propagate(shape, end_frame, *, included_frames=None):
930
958
prev_shape = None
931
959
for shape in sorted (track ["shapes" ], key = lambda shape : shape ["frame" ]):
932
960
curr_frame = shape ["frame" ]
933
- if included_frames is not None and curr_frame not in included_frames :
961
+ if curr_frame in deleted_frames :
934
962
continue
935
963
if prev_shape and end_frame <= curr_frame :
936
964
# If we exceed the end_frame and there was a previous shape,
@@ -982,6 +1010,8 @@ def propagate(shape, end_frame, *, included_frames=None):
982
1010
shapes = [
983
1011
shape for shape in shapes
984
1012
1013
+ if shape ["frame" ] not in deleted_frames
1014
+
985
1015
# After interpolation there can be a finishing frame
986
1016
# outside of the task boundaries. Filter it out to avoid errors.
987
1017
# https://github.com/openvinotoolkit/cvat/issues/2827
0 commit comments