5858
5959_TaskIdToValidationLayout = dict [int , dict ]
6060_TaskIdToHoneypotsMapping = dict [int , dict ]
61+ _TaskIdToSequenceOfFrameNames = dict [int , list [str ]]
6162
6263_HoneypotFrameId = int
6364_ValidationFrameId = int
@@ -72,6 +73,7 @@ class _ValidationResult:
7273 gt_stats : GtStats
7374 task_id_to_val_layout : _TaskIdToValidationLayout
7475 task_id_to_honeypots_mapping : _TaskIdToHoneypotsMapping
76+ task_id_to_sequence_of_frame_names : _TaskIdToSequenceOfFrameNames
7577
7678
7779T = TypeVar ("T" )
@@ -141,23 +143,33 @@ def _validate_jobs(self):
141143 task_id_to_val_layout : dict [int , cvat_api .models .TaskValidationLayoutRead ] = {}
142144 task_id_to_honeypots_mapping : dict [int , _HoneypotFrameToValFrame ] = {}
143145
146+ # store sequence of frame names for each task
147+ # task honeypot with frame index matches the sequence[index]
148+ task_id_to_sequence_of_frame_names : dict [int , list [str ]] = {}
149+
144150 min_quality = manifest .validation .min_quality
145151
146152 job_id_to_quality_report : dict [int , cvat_api .models .QualityReport ] = {}
147153
148154 for cvat_task_id in cvat_task_ids :
155+ # obtain quality report details
149156 task_quality_report = cvat_api .get_task_quality_report (cvat_task_id )
150157 task_quality_report_data = cvat_api .get_quality_report_data (task_quality_report .id )
151158 task_id_to_quality_report_data [cvat_task_id ] = task_quality_report_data
152159
160+ # obtain task validation layout and define honeypots mapping
153161 task_val_layout = cvat_api .get_task_validation_layout (cvat_task_id )
154162 honeypot_frame_to_real = {
155163 f : task_val_layout .honeypot_real_frames [idx ]
156164 for idx , f in enumerate (task_val_layout .honeypot_frames )
157165 }
158166 task_id_to_val_layout [cvat_task_id ] = task_val_layout
159167 task_id_to_honeypots_mapping [cvat_task_id ] = honeypot_frame_to_real
168+ task_id_to_sequence_of_frame_names [cvat_task_id ] = [
169+ frame .name for frame in cvat_api .get_task_data_meta (cvat_task_id ).frames
170+ ]
160171
172+ # obtain quality reports for each job from the task
161173 job_id_to_quality_report .update (
162174 {
163175 quality_report .job_id : quality_report
@@ -172,28 +184,28 @@ def _validate_jobs(self):
172184
173185 # assess quality of the job's honeypots
174186 task_quality_report_data = task_id_to_quality_report_data [cvat_task_id ]
187+ sorted_task_frame_names = task_id_to_sequence_of_frame_names [cvat_task_id ]
175188 task_honeypots = {int (frame ) for frame in task_quality_report_data .frame_results }
176189 honeypots_mapping = task_id_to_honeypots_mapping [cvat_task_id ]
177190
178191 for honeypot in task_honeypots & set (job_meta .job_frame_range ):
179192 val_frame = honeypots_mapping [honeypot ]
193+ val_frame_name = sorted_task_frame_names [val_frame ]
180194
181195 result = task_quality_report_data .frame_results [str (honeypot )]
182- self ._gt_stats .setdefault ((cvat_task_id , val_frame ), ValidationFrameStats ())
183- self ._gt_stats [
184- (cvat_task_id , val_frame )
185- ].accumulated_quality += result .annotations .accuracy
196+ self ._gt_stats .setdefault (val_frame_name , ValidationFrameStats ())
197+ self ._gt_stats [val_frame_name ].accumulated_quality += result .annotations .accuracy
186198
187199 if result .annotations .accuracy < min_quality :
188- self ._gt_stats [( cvat_task_id , val_frame ) ].failed_attempts += 1
200+ self ._gt_stats [val_frame_name ].failed_attempts += 1
189201 else :
190- self ._gt_stats [( cvat_task_id , val_frame ) ].accepted_attempts += 1
202+ self ._gt_stats [val_frame_name ].accepted_attempts += 1
191203
192204 # assess job quality
193205 job_quality_report = job_id_to_quality_report [cvat_job_id ]
194206
195207 accuracy = job_quality_report .summary .accuracy
196- if isinstance ( accuracy , int ) :
208+ if not job_quality_report . summary . gt_count :
197209 assert accuracy == 0
198210 job_results [cvat_job_id ] = self .UNKNOWN_QUALITY
199211 rejected_jobs [cvat_job_id ] = TooFewGtError
@@ -208,6 +220,7 @@ def _validate_jobs(self):
208220 self ._rejected_jobs = rejected_jobs
209221 self ._task_id_to_val_layout = task_id_to_val_layout
210222 self ._task_id_to_honeypots_mapping = task_id_to_honeypots_mapping
223+ self ._task_id_to_sequence_of_frame_names = task_id_to_sequence_of_frame_names
211224
212225 def _restore_original_image_paths (self , merged_dataset : dm .Dataset ) -> dm .Dataset :
213226 class RemoveCommonPrefix (dm .ItemTransform ):
@@ -320,6 +333,9 @@ def validate(self) -> _ValidationResult:
320333 gt_stats = self ._require_field (self ._gt_stats ),
321334 task_id_to_val_layout = self ._require_field (self ._task_id_to_val_layout ),
322335 task_id_to_honeypots_mapping = self ._require_field (self ._task_id_to_honeypots_mapping ),
336+ task_id_to_sequence_of_frame_names = self ._require_field (
337+ self ._task_id_to_sequence_of_frame_names
338+ ),
323339 )
324340
325341
@@ -359,7 +375,7 @@ def process_intermediate_results( # noqa: PLR0912
359375 logger .debug ("Task id %s, %s" , getattr (task , "id" , None ), getattr (task , "__dict__" , None ))
360376
361377 gt_stats = {
362- ( gt_image_stat .cvat_task_id , gt_image_stat . gt_frame_id ) : ValidationFrameStats (
378+ gt_image_stat .gt_frame_name : ValidationFrameStats (
363379 failed_attempts = gt_image_stat .failed_attempts ,
364380 accepted_attempts = gt_image_stat .accepted_attempts ,
365381 accumulated_quality = gt_image_stat .accumulated_quality ,
@@ -391,9 +407,8 @@ def process_intermediate_results( # noqa: PLR0912
391407
392408 gt_stats = validation_result .gt_stats
393409 if gt_stats :
394- cvat_task_id_to_failed_val_frames : dict [
395- int , set [int ]
396- ] = {} # cvat_task_id: {val_frame_id, ...}
410+ # cvat_task_id: {val_frame_id, ...}
411+ cvat_task_id_to_failed_val_frames : dict [int , set [int ]] = {}
397412 rejected_job_ids = rejected_jobs .keys ()
398413
399414 if rejected_job_ids :
@@ -411,9 +426,13 @@ def process_intermediate_results( # noqa: PLR0912
411426 for honeypot , val_frame in honeypots_mapping .items ()
412427 if honeypot in job_honeypots
413428 ]
429+ sorted_task_frame_names = validation_result .task_id_to_sequence_of_frame_names [
430+ cvat_task_id
431+ ]
414432
415433 for val_frame in validation_frames :
416- val_frame_stats = gt_stats [(cvat_task_id , val_frame )]
434+ val_frame_name = sorted_task_frame_names [val_frame ]
435+ val_frame_stats = gt_stats [val_frame_name ]
417436 if (
418437 val_frame_stats .failed_attempts >= Config .validation .gt_ban_threshold
419438 and not val_frame_stats .accepted_attempts
@@ -439,9 +458,11 @@ def process_intermediate_results( # noqa: PLR0912
439458 )
440459
441460 updated_task_honeypot_real_frames = task_validation_layout .honeypot_real_frames .copy ()
442- task_honeypot_real_frames_index = {
443- f : idx for idx , f in enumerate (updated_task_honeypot_real_frames )
444- }
461+
462+ # validation frames may be repeated
463+ task_honeypot_real_frames_index : dict [int , list [int ]] = {}
464+ for idx , f in enumerate (updated_task_honeypot_real_frames ):
465+ task_honeypot_real_frames_index .setdefault (f , []).append (idx )
445466
446467 rejected_jobs_for_task = [
447468 j
@@ -478,7 +499,7 @@ def process_intermediate_results( # noqa: PLR0912
478499 for prev_val_frame , new_val_frame in zip (
479500 validation_frames_to_replace , new_validation_frames , strict = True
480501 ):
481- idx = task_honeypot_real_frames_index [prev_val_frame ]
502+ idx = task_honeypot_real_frames_index [prev_val_frame ]. pop ( 0 )
482503 updated_task_honeypot_real_frames [idx ] = new_val_frame
483504 except ValueError as ex :
484505 logger .exception (
0 commit comments