| from logging import getLogger |
|
|
| from scorevision.vlm_pipeline.domain_specific_schemas.challenge_types import ( |
| ChallengeType, |
| parse_challenge_type, |
| ) |
| from scorevision.utils.data_models import ( |
| SVChallenge, |
| SVRunOutput, |
| SVEvaluation, |
| TotalScore, |
| ) |
| from scorevision.chute_template.schemas import TVPredictInput |
|
|
| from scorevision.vlm_pipeline.non_vlm_scoring.keypoints import evaluate_keypoints |
| from scorevision.vlm_pipeline.non_vlm_scoring.objects import ( |
| compare_object_counts, |
| compare_team_labels, |
| compare_object_labels, |
| compare_object_placement, |
| ) |
|
|
| from scorevision.utils.settings import get_settings |
| from scorevision.utils.video_processing import FrameStore |
| from scorevision.vlm_pipeline.utils.data_models import ( |
| PseudoGroundTruth, |
| MinerScore, |
| AggregatedScore, |
| ) |
| from scorevision.vlm_pipeline.utils.response_models import ( |
| FrameAnnotation, |
| BoundingBox, |
| ShirtColor, |
| TEAM1_SHIRT_COLOUR, |
| TEAM2_SHIRT_COLOUR, |
| ) |
| from scorevision.vlm_pipeline.domain_specific_schemas.football import ( |
| Person as ObjectOfInterest, |
| OBJECT_ID_LOOKUP, |
| ) |
| from scorevision.vlm_pipeline.domain_specific_schemas.football import Action |
| from scorevision.vlm_pipeline.non_vlm_scoring.smoothness import bbox_smoothness_per_type |
|
|
| logger = getLogger(__name__) |
|
|
|
|
| def parse_miner_prediction(miner_run: SVRunOutput) -> dict[int, dict]: |
| predicted_frames = ( |
| (miner_run.predictions or {}).get("frames") if miner_run.predictions else None |
| ) or [] |
| logger.info(f"Miner predicted {len(predicted_frames)} frames") |
|
|
| miner_annotations = {} |
| for predicted_frame in predicted_frames: |
| bboxes = [] |
| frame_number = predicted_frame.get("frame_id", -1) |
| for bbox in predicted_frame.get("boxes", []) or []: |
| try: |
| raw_cls = bbox.get("cls_id") |
| try: |
| object_id = int(raw_cls) |
| except (TypeError, ValueError): |
| object_id = None |
|
|
| looked_up = ( |
| OBJECT_ID_LOOKUP.get(object_id) if object_id is not None else None |
| ) |
|
|
| object_type: ObjectOfInterest |
| object_colour: ShirtColor = ShirtColor.OTHER |
|
|
| if looked_up is None: |
| object_type = ObjectOfInterest.PLAYER |
|
|
| elif isinstance(looked_up, str): |
| team_str = looked_up.strip().lower().replace(" ", "") |
| object_type = ObjectOfInterest.PLAYER |
| if team_str == "team1": |
| object_colour = TEAM1_SHIRT_COLOUR |
| elif team_str == "team2": |
| object_colour = TEAM2_SHIRT_COLOUR |
| else: |
| object_colour = ShirtColor.OTHER |
|
|
| else: |
| object_type = looked_up |
| team_field = ( |
| (bbox.get("team") or bbox.get("team_id") or "").strip().lower() |
| ) |
| if team_field in {"1", "team1"}: |
| object_colour = TEAM1_SHIRT_COLOUR |
| elif team_field in {"2", "team2"}: |
| object_colour = TEAM2_SHIRT_COLOUR |
| else: |
| object_colour = ShirtColor.OTHER |
|
|
| bboxes.append( |
| BoundingBox( |
| bbox_2d=[ |
| int(bbox["x1"]), |
| int(bbox["y1"]), |
| int(bbox["x2"]), |
| int(bbox["y2"]), |
| ], |
| label=object_type, |
| cluster_id=object_colour, |
| ) |
| ) |
| except Exception as e: |
| logger.error(e) |
| continue |
| miner_annotations[frame_number] = { |
| "bboxes": bboxes, |
| "action": predicted_frame.get("action", None), |
| "keypoints": predicted_frame.get("keypoints", []), |
| } |
| return miner_annotations |
|
|
|
|
| def post_vlm_ranking( |
| payload: TVPredictInput, |
| miner_run: SVRunOutput, |
| challenge: SVChallenge, |
| pseudo_gt_annotations: list[PseudoGroundTruth], |
| frame_store: FrameStore, |
| ) -> SVEvaluation: |
| score_breakdown = TotalScore() |
| settings = get_settings() |
| miner_annotations = parse_miner_prediction(miner_run=miner_run) |
| logger.info(payload.meta) |
|
|
| challenge_type = challenge.challenge_type |
| if challenge_type is None: |
| challenge_type = parse_challenge_type(payload.meta.get("challenge_type")) |
|
|
| if ( |
| miner_run.success |
| and len(miner_annotations) == settings.SCOREVISION_VIDEO_MAX_FRAME_NUMBER |
| and challenge_type is not None |
| ): |
| score_breakdown.keypoints.floor_markings_alignment = evaluate_keypoints( |
| frames=frame_store, |
| miner_predictions=miner_annotations, |
| challenge_type=challenge_type, |
| ) |
| score_breakdown.objects.bbox_placement = compare_object_placement( |
| pseudo_gt=pseudo_gt_annotations, miner_predictions=miner_annotations |
| ) |
| score_breakdown.objects.categorisation = compare_object_labels( |
| pseudo_gt=pseudo_gt_annotations, miner_predictions=miner_annotations |
| ) |
| score_breakdown.objects.team = compare_team_labels( |
| pseudo_gt=pseudo_gt_annotations, miner_predictions=miner_annotations |
| ) |
| score_breakdown.objects.enumeration = compare_object_counts( |
| pseudo_gt=pseudo_gt_annotations, miner_predictions=miner_annotations |
| ) |
| score_breakdown.objects.tracking_stability = bbox_smoothness_per_type( |
| video_bboxes=[ |
| miner_annotations[frame_num]["bboxes"] |
| for frame_num in sorted(miner_annotations.keys()) |
| ], |
| image_height=settings.SCOREVISION_IMAGE_HEIGHT, |
| image_width=settings.SCOREVISION_IMAGE_WIDTH, |
| ) |
| score_breakdown.latency.inference = 1 / 2 ** (miner_run.latency_ms / 1000) |
| else: |
| logger.info( |
| f"Miner success={miner_run.success} frames={len(miner_annotations)} " |
| f"challenge_type={getattr(challenge_type, 'value', None)} (must not be None)." |
| ) |
|
|
| breakdown_dict = score_breakdown.to_dict() |
| objects_dict = breakdown_dict.get("objects", {}) or {} |
| keypoints_dict = breakdown_dict.get("keypoints", {}) or {} |
|
|
| def _mean_defined(values) -> float: |
| nums = [v for v in values if isinstance(v, (int, float))] |
| return (sum(nums) / len(nums)) if nums else 0.0 |
|
|
| objects_score = _mean_defined(objects_dict.values()) |
| keypoints_score = _mean_defined(keypoints_dict.values()) |
| final_score = 0.5 * objects_score + 0.5 * keypoints_score |
|
|
| details = { |
| "breakdown": breakdown_dict, |
| "group_scores": { |
| "objects": objects_score, |
| "keypoints": keypoints_score, |
| }, |
| "challenge": { |
| "id_hash": challenge.challenge_id, |
| "api_task_id": challenge.api_task_id, |
| "type": getattr(challenge.challenge_type, "value", None), |
| }, |
| "prompt": challenge.prompt, |
| } |
| logger.info(details) |
|
|
| return SVEvaluation( |
| acc_breakdown=breakdown_dict, |
| latency_ms=miner_run.latency_ms, |
| acc=final_score, |
| score=final_score, |
| details=details, |
| ) |
|
|