{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# References\n\n- https://www.kaggle.com/code/robikscube/nfl-player-contact-detection-getting-started","metadata":{}},{"cell_type":"markdown","source":"# Problem Statement","metadata":{}},{"cell_type":"markdown","source":"## Goal of the Competition\nThe goal of this competition is to detect external contact experienced by players during an NFL football game. You will use video and player tracking data to identify moments with contact to help improve player safety.\n\n## Context\nThe National Football League (NFL) has teamed up with Amazon Web Services (AWS) to strengthen its commitment to predict player injuries. The NFL aspires to have the best injury surveillance and mitigation program in any sport. With your machine learning and computer vision skills, you can help the NFL accurately identify when players experience contact throughout a football play.\n\nIn prior years, the NFL challenged the Kaggle community to create helmet impact detection and identification algorithms. This year the NFL looks to automatically identify all moments when players experience contact. This competition will be successful if we can reliably detect moments when players are in contact with one another and when a player’s body is in contact with the ground.\n\nCurrently, the NFL uses its tracking system to monitor a large number of statistics about players’ load during the season. The league has a solution that predicts contact between players, but it only leverages the player tracking data. This competition hopes to improve the predictive power by including video in addition to tracking data. Categorizing ground contact will also provide a more comprehensive view of impacts, improving analysis for player health and safety.\n\nMore accurate data is an important step toward the NFL’s injury surveillance and mitigation goals. With complete contact detection, the league can identify correlations between certain types of contact and injury, a contributor to future prevention. Your efforts could help mitigate unsafe situations to reduce injury to all players.\n\nThe National Football League is America's most popular sports league. Founded in 1920, the NFL developed the model for the successful modern sports league and is committed to advancing progress in the diagnosis, prevention, and treatment of sports-related injuries. This competition is part of the Digital Athlete, a joint effort between the NFL and AWS to build a virtual, 360-degree representation of an NFL player’s experience. The Digital Athlete hopes to generate a precise picture of what they need when it comes to preventing and recovering from injuries while performing at their best. Health and safety efforts include support for independent medical research and engineering advancements as well as a commitment to work to better protect players and make the game safer, including enhancements to medical protocols and improvements to how our game is taught and played. For more information about the NFL's health and safety efforts, please visit the [NFL Player Health and Safety website](https://www.nfl.com/playerhealthandsafety/).\n\n## Evaluation\nSubmissions are evaluated on [Matthews Correlation Coefficient](https://en.wikipedia.org/wiki/Phi_coefficient) between the predicted and actual contact events.\n\n$$MCC = \\frac{TP * TN - FP * FN}{\\sqrt{(TP + FP)(TP + FN)(TN + FP)(TN + FN)}}$$\n\nFor every allowable `1contact_id` (formed by concatenating the `game_play_step_player1_player2`), you must predict whether the involved players are in contact at that moment in time. sample_submission.csv provides the exhaustive list of `contact_ids`. Note that the ground, denoted as player G, is included as a possible contact in place of player2. The player with the lower id is always listed first in the `contact_id`.\n\nThe file should contain a header and have the following format:\n\n```\ncontact_id,contact\n58168_003392_0_38590_43854,0\n58168_003392_0_38590_41257,1\n58168_003392_0_38590_41944,0\netc.\n```","metadata":{}},{"cell_type":"markdown","source":"# Import dependencies and load data","metadata":{}},{"cell_type":"code","source":"# import dependencies\nimport numpy as np\nimport pandas as pd\nimport os\nimport cv2\nimport subprocess\n\n# IPython\nimport IPython\nfrom IPython.display import Video, display\n\n# matplotlib\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom matplotlib.markers import MarkerStyle\nimport matplotlib.animation as animation","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:31:18.040757Z","iopub.execute_input":"2023-01-20T08:31:18.041442Z","iopub.status.idle":"2023-01-20T08:31:18.049016Z","shell.execute_reply.started":"2023-01-20T08:31:18.041400Z","shell.execute_reply":"2023-01-20T08:31:18.047482Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# paths\n\nTEST_DIR = '/kaggle/input/nfl-player-contact-detection/test'\nTRAIN_DIR = '/kaggle/input/nfl-player-contact-detection/train'\n\nTEST_BASELINE_HELMETS = '/kaggle/input/nfl-player-contact-detection/test_baseline_helmets.csv'\nTRAIN_BASELINE_HELMETS = '/kaggle/input/nfl-player-contact-detection/train_baseline_helmets.csv'\n\nTEST_PLAYER_TRACKING = '/kaggle/input/nfl-player-contact-detection/test_player_tracking.csv'\nTRAIN_PLAYER_TRACKING = '/kaggle/input/nfl-player-contact-detection/train_player_tracking.csv'\n\nTEST_VIDEO_METADATA = '/kaggle/input/nfl-player-contact-detection/test_video_metadata.csv'\nTRAIN_VIDEO_METADATA = '/kaggle/input/nfl-player-contact-detection/train_video_metadata.csv'\n\nTRAIN_LABELS = '/kaggle/input/nfl-player-contact-detection/train_labels.csv'\nSAMPLE_SUBMISSION = '/kaggle/input/nfl-player-contact-detection/sample_submission.csv'","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:31:20.801117Z","iopub.execute_input":"2023-01-20T08:31:20.801776Z","iopub.status.idle":"2023-01-20T08:31:20.811423Z","shell.execute_reply.started":"2023-01-20T08:31:20.801726Z","shell.execute_reply":"2023-01-20T08:31:20.809176Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# TRAIN_BASELINE_HELMETS\n\ntrain_baseline_helmets_data = pd.read_csv(TRAIN_BASELINE_HELMETS)\nprint(train_baseline_helmets_data.shape)\ntrain_baseline_helmets_data.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:31:21.285739Z","iopub.execute_input":"2023-01-20T08:31:21.286166Z","iopub.status.idle":"2023-01-20T08:31:25.997961Z","shell.execute_reply.started":"2023-01-20T08:31:21.286133Z","shell.execute_reply":"2023-01-20T08:31:25.996735Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# TEST_BASELINE_HELMETS\n\ntest_baseline_helmets_data = pd.read_csv(TEST_BASELINE_HELMETS)\nprint(test_baseline_helmets_data.shape)\ntest_baseline_helmets_data.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:31:26.000555Z","iopub.execute_input":"2023-01-20T08:31:26.001075Z","iopub.status.idle":"2023-01-20T08:31:26.091326Z","shell.execute_reply.started":"2023-01-20T08:31:26.001027Z","shell.execute_reply":"2023-01-20T08:31:26.089432Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# TRAIN_PLAYER_TRACKING\n\ntrain_player_tracking_data = pd.read_csv(TRAIN_PLAYER_TRACKING, parse_dates=['datetime'])\nprint(train_player_tracking_data.shape)\ntrain_player_tracking_data.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:31:26.093413Z","iopub.execute_input":"2023-01-20T08:31:26.093971Z","iopub.status.idle":"2023-01-20T08:31:36.233271Z","shell.execute_reply.started":"2023-01-20T08:31:26.093919Z","shell.execute_reply":"2023-01-20T08:31:36.231855Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# TEST_PLAYER_TRACKING\n\ntest_player_tracking_data = pd.read_csv(TEST_PLAYER_TRACKING, parse_dates=['datetime'])\nprint(test_player_tracking_data.shape)\ntest_player_tracking_data.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:31:36.236433Z","iopub.execute_input":"2023-01-20T08:31:36.236946Z","iopub.status.idle":"2023-01-20T08:31:36.352469Z","shell.execute_reply.started":"2023-01-20T08:31:36.236896Z","shell.execute_reply":"2023-01-20T08:31:36.350429Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# TRAIN_VIDEO_METADATA\n\ntrain_video_metadata = pd.read_csv(TRAIN_VIDEO_METADATA, parse_dates=['start_time', 'end_time', 'snap_time'])\nprint(train_video_metadata.shape)\ntrain_video_metadata.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:31:36.354106Z","iopub.execute_input":"2023-01-20T08:31:36.354515Z","iopub.status.idle":"2023-01-20T08:31:36.396888Z","shell.execute_reply.started":"2023-01-20T08:31:36.354478Z","shell.execute_reply":"2023-01-20T08:31:36.395256Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# TEST_VIDEO_METADATA\n\ntest_video_metadata = pd.read_csv(TEST_VIDEO_METADATA, parse_dates=['start_time', 'end_time', 'snap_time'])\nprint(test_video_metadata.shape)\ntest_video_metadata.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:31:36.399748Z","iopub.execute_input":"2023-01-20T08:31:36.400308Z","iopub.status.idle":"2023-01-20T08:31:36.432896Z","shell.execute_reply.started":"2023-01-20T08:31:36.400254Z","shell.execute_reply":"2023-01-20T08:31:36.431237Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# TRAIN_LABELS\n\ntrain_labels_data = pd.read_csv(TRAIN_LABELS, parse_dates=['datetime'])\nprint(train_labels_data.shape)\ntrain_labels_data.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:31:36.435197Z","iopub.execute_input":"2023-01-20T08:31:36.435865Z","iopub.status.idle":"2023-01-20T08:32:04.465793Z","shell.execute_reply.started":"2023-01-20T08:31:36.435808Z","shell.execute_reply":"2023-01-20T08:32:04.463543Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# SAMPLE_SUBMISSION\n\nsample_submission = pd.read_csv(SAMPLE_SUBMISSION)\nprint(sample_submission.shape)\nsample_submission.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:32:04.467376Z","iopub.execute_input":"2023-01-20T08:32:04.467740Z","iopub.status.idle":"2023-01-20T08:32:04.516099Z","shell.execute_reply.started":"2023-01-20T08:32:04.467708Z","shell.execute_reply":"2023-01-20T08:32:04.514912Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n# EDA","metadata":{}},{"cell_type":"markdown","source":"## Play video","metadata":{}},{"cell_type":"code","source":"def play_video(video_path: str):\n    frac = 0.65 # scaling factor for display \n    display(\n        Video(data=video_path, embed=True, height=int(720*frac), width=int(1280*frac))\n    )","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:32:04.518032Z","iopub.execute_input":"2023-01-20T08:32:04.518537Z","iopub.status.idle":"2023-01-20T08:32:04.525941Z","shell.execute_reply.started":"2023-01-20T08:32:04.518488Z","shell.execute_reply":"2023-01-20T08:32:04.524681Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"video_path = '/kaggle/input/nfl-player-contact-detection/train/58168_003392_Endzone.mp4'\nplay_video(video_path)","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:32:04.531257Z","iopub.execute_input":"2023-01-20T08:32:04.532304Z","iopub.status.idle":"2023-01-20T08:32:04.673823Z","shell.execute_reply.started":"2023-01-20T08:32:04.532248Z","shell.execute_reply":"2023-01-20T08:32:04.672753Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Video annotated with helmets","metadata":{}},{"cell_type":"code","source":"def annotate_video_with_helmets(video_path: str, baseline_boxes:pd.DataFrame, verbose=True) -> str:\n    \"\"\"\n    Annotates a video with baseline model boxes and labels.\n    \"\"\"\n    video_name = video_path.split('/')[-1]\n    play_name = video_name.split('.')[0]\n    HELMET_COLOR = (0, 0, 0) # black\n    baseline_boxes = baseline_boxes.query(\"video==@video_name\")\n    \n    # verbose\n    if verbose==True:\n        print(f\"Running for {video_name}\")\n    \n    # VideoCapture Object\n    cap  = cv2.VideoCapture(video_path)\n    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n    fps = cap.get(cv2.CAP_PROP_FPS)\n    \n    # VideoWriter Object\n    output_path = \"labeled_\" + video_name\n    tmp_output_path = \"tmp_\" + output_path\n    out = cv2.VideoWriter(tmp_output_path, cv2.VideoWriter_fourcc(*'MP4V'), \n                          fps, (width, height))\n    \n    # check  if camera opened successfully\n    if cap.isOpened() == False:\n        print('Error opening file')\n        \n    # read until video is complete\n    frame  = 1\n    while(cap.isOpened()):\n        # capture frame by frame\n        ret, img = cap.read()\n        \n        if ret==True:\n            # add play_name text\n            cv2.putText(img, play_name,\n                       (int(0.01*width), int(0.05*height)),\n                        cv2.FONT_HERSHEY_SIMPLEX,\n                        1,\n                        HELMET_COLOR,\n                        1\n                       )\n            \n            # add frame counter\n            cv2.putText(img, \"Frame: \"+str(frame),\n                       (int(0.75*width), int(0.05*height)),\n                        cv2.FONT_HERSHEY_SIMPLEX,\n                        1,\n                        HELMET_COLOR,\n                        1\n                       )\n\n            # adding helmet bounding boxes and player tags\n            bbox_set = baseline_boxes.query(\"frame==@frame\")\n            for i, annot in enumerate(zip(np.array(bbox_set[['player_label','left','width','top','height']]))):\n                player_label, bbox_left, bbox_width, bbox_top, bbox_height = annot[0]\n                # add helmet bbox\n                cv2.rectangle(img, \n                              (bbox_left,bbox_top), (bbox_left+bbox_width, bbox_top+bbox_height),\n                              HELMET_COLOR,\n                              1\n                             )\n                # add player label\n                cv2.putText(img, player_label,\n                       (bbox_left+10, bbox_top+10),\n                        cv2.FONT_HERSHEY_SIMPLEX,\n                        1,\n                        HELMET_COLOR,\n                        1\n                       )\n            \n            frame += 1\n            out.write(img) \n                \n        # break the loop   \n        else: \n            break\n        \n        \n    out.release()\n    \n    # Not all browsers support the codec, we will re-load the file at tmp_output_path\n    # and convert to a codec that is more broadly readable using ffmpeg\n    if os.path.exists(output_path):\n        os.remove(output_path)\n    subprocess.run(\n        [\n            \"ffmpeg\",\n            \"-i\",\n            tmp_output_path,\n            \"-crf\",\n            \"18\",\n            \"-preset\",\n            \"veryfast\",\n            \"-hide_banner\",\n            \"-loglevel\",\n            \"error\",\n            \"-vcodec\",\n            \"libx264\",\n            output_path,\n        ]\n    )\n    os.remove(tmp_output_path)\n    \n    return output_path","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:32:04.675280Z","iopub.execute_input":"2023-01-20T08:32:04.675869Z","iopub.status.idle":"2023-01-20T08:32:04.700837Z","shell.execute_reply.started":"2023-01-20T08:32:04.675830Z","shell.execute_reply":"2023-01-20T08:32:04.699730Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path = annotate_video_with_helmets(video_path, train_baseline_helmets_data)\nplay_video(path)","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:32:04.702340Z","iopub.execute_input":"2023-01-20T08:32:04.703516Z","iopub.status.idle":"2023-01-20T08:32:24.685165Z","shell.execute_reply.started":"2023-01-20T08:32:04.703472Z","shell.execute_reply":"2023-01-20T08:32:24.683692Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Player tracking data","metadata":{}},{"cell_type":"code","source":"def create_football_field(fig, ax, line_color='black', field_color='white'):\n    \"\"\"\n    Function that plots the football field for viewing players.\n    \"\"\"\n    \n    # set field dimensions\n    plt.xlim(0,120)\n    plt.ylim(0,53.3)\n    \n    # adding rectangles to the field\n    for i in range(12):\n        rect = patches.Rectangle((10*i,0), 10, 53.3, linewidth=1, edgecolor=line_color, facecolor=field_color)\n        ax.add_patch(rect)\n    \n    # configure axes\n    ax.tick_params(\n        axis='both',\n        which='both',\n        direction='in',\n        pad=-40,\n        length = 5,\n        bottom=True,\n        top=True,\n        labeltop=True,\n        labelbottom=True,\n        left=False,\n        right=False,\n        labelleft=False,\n        labelright=False,\n        color=line_color)\n    \n    # set ticks on the side of the field\n    ax.set_xticks([i for i in range(10,111)])\n    \n    # setting yard marking\n    label_set = []\n    for i in range(1,10):\n        if i<=5:\n            label_set += [\" \" for j in range(9)] + [str(i*10)]\n        else:\n            label_set += [\" \" for j in range(9)] + [str((10-i)*10)]\n    label_set =  [\" \"] + label_set + [\" \" for j in range(10)]\n    ax.set_xticklabels(label_set, fontsize=20, color=line_color)\n    \n    \n    return fig, ax","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:32:24.687097Z","iopub.execute_input":"2023-01-20T08:32:24.687519Z","iopub.status.idle":"2023-01-20T08:32:24.716199Z","shell.execute_reply.started":"2023-01-20T08:32:24.687480Z","shell.execute_reply":"2023-01-20T08:32:24.715041Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def populate_field(play_name:str, step:np.int64, player_tracking_data:pd.DataFrame, home_color:str='violet', away_color:str='coral'):\n    \"\"\"\n    populates the field with player tracking data of a play_name and step.\n    \"\"\"\n    # subset data to current play and step \n    step_info = player_tracking_data.query(\"game_play==@play_name and step==@step \").copy()\n    \n    # create new field\n    fig, ax= plt.subplots(figsize=(12, 5.33))\n    fig, ax  = create_football_field(fig, ax)\n    \n    # set title\n    ax.set_title(f'Tracking data for {play_name} at step {step}')\n    \n    # populate field with players\n    for row in step_info.iterrows():\n        if row[1]['team'] == 'home':\n            color = home_color\n        else:\n            color = away_color\n        marker1 = MarkerStyle(r'$\\spadesuit$')\n        marker1._transform.rotate_deg(360-row[1]['orientation'])\n        ax.scatter(row[1]['x_position'], row[1]['y_position'], marker=marker1, s=150, color=color)    \n    \n    plt.close()\n    return fig, ax","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:32:24.717798Z","iopub.execute_input":"2023-01-20T08:32:24.718188Z","iopub.status.idle":"2023-01-20T08:32:24.738451Z","shell.execute_reply.started":"2023-01-20T08:32:24.718153Z","shell.execute_reply":"2023-01-20T08:32:24.737073Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, ax = populate_field(play_name='58168_003392', step=1, player_tracking_data=train_player_tracking_data)\nfig","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:32:24.739703Z","iopub.execute_input":"2023-01-20T08:32:24.740055Z","iopub.status.idle":"2023-01-20T08:32:28.605725Z","shell.execute_reply.started":"2023-01-20T08:32:24.740024Z","shell.execute_reply":"2023-01-20T08:32:28.603777Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# arguments\nplay_name = '58168_003392'\ndata = train_player_tracking_data.copy()\n\n# total number of frames at 10hz\nframes = len(data.query(\"game_play==@play_name\")['step'].unique())\n\n# frequency modifier factor [min_val:1, max_val:10] (reduce this for smoother tracking but longer rendering time)\nfreq_mod_fac = 5\n# to reduce rendering time we change the frequency to (10/freq_mod_fac)Hz\nframes = int(frames/freq_mod_fac)\ninterval_ms = int(100*freq_mod_fac)\n\n# initialize figure\nfig, ax = plt.subplots(figsize=(12, 5.33))\nfig, ax = create_football_field(fig, ax)\n\ndef animate(i:int, play_name:str, player_tracking_data:pd.DataFrame, frames, home_color:str='violet', away_color:str='coral'):\n    \"\"\"\n    Function to animate player tracking data\n    \"\"\"\n    # create fresh field\n    ax.clear()\n    create_football_field(fig, ax)\n    \n    # find appropriate\n    play_info = player_tracking_data.query(\"game_play==@play_name\").copy()\n    step_list = np.linspace(play_info['step'].min(), play_info['step'].max(), frames)\n    step = int(step_list[i])\n    \n    # subset data to step info\n    step_info = play_info.query(\"step==@step\").copy()\n    \n    # iterate step info to populate field\n    for row in step_info.iterrows():\n        if row[1]['team'] == 'home':\n            color = home_color\n        else:\n            color = away_color\n        marker1 = MarkerStyle(r'$\\spadesuit$')\n        marker1._transform.rotate_deg(360-row[1]['orientation'])\n        ax.scatter(row[1]['x_position'], row[1]['y_position'], marker=marker1, s=150, color=color)\n    \n    # set axis title\n    ax.set_title(f'Tracking data for {play_name} at step {step}')\n\n# animate\nanim = animation.FuncAnimation(fig, animate, fargs=(play_name, data, frames),frames=frames, repeat=False, interval=interval_ms)\n\n# embed html video to notebook\nvideo = anim.to_html5_video()\nhtml = IPython.display.HTML(video)\ndisplay(html)\nplt.close()","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:32:28.607048Z","iopub.execute_input":"2023-01-20T08:32:28.607404Z","iopub.status.idle":"2023-01-20T08:34:27.699289Z","shell.execute_reply.started":"2023-01-20T08:32:28.607372Z","shell.execute_reply":"2023-01-20T08:34:27.697964Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Video annotated with hemets and contact labels\nThe following colors are used to indicate labels:\n- __Black__ helmet boxes indicate that the player is not in contact. A unique number (home/visiting combined with jersey number) is shown next to their helmet.\n- __Green__ helmet boxes indicate that the player is in contact with one or more players.\n- __Red__ helmet boxes indicates that the player is in contact with the ground (and possibly another player).\n- __Blue__ lines show the link between players in contact with each other.","metadata":{}},{"cell_type":"code","source":"def join_helmets_contact(play_name, labels, helmets, meta, view, fps=59.94):\n    \"\"\"\n    Joins helmets and labels for a given play_name, Results can be \n    used for visualizing labels.\n    Returns a dataframe with the joint dataframe, duplicating rows if \n    multiple contacts occur.\n    \"\"\"\n    # labels and helmets for specific play_name\n    labels = labels.query('game_play==@play_name').copy()\n    helmets = helmets.query('game_play==@play_name and view==@view').copy()\n    \n    # start time of the play\n    start_time = meta.query('game_play==@play_name and view==@view')['start_time'].values[0]\n    \n    # converting frame into datetime in helmets data for merge\n    helmets['datetime'] = pd.to_timedelta(helmets['frame'] * (1/fps), unit='s') + start_time\n    helmets['datetime'] = pd.to_datetime(helmets['datetime'], utc=True)\n    \n    helmets['datetime_ngs'] = pd.DatetimeIndex(helmets['datetime'] + pd.to_timedelta(50, 'ms')).floor('100ms').values\n    helmets['datetime_ngs'] = pd.to_datetime(helmets['datetime_ngs'], utc=True)\n    \n    \n    # converting datetime in lables for merge\n    labels['datetime_ngs'] = pd.to_datetime(labels['datetime'], utc=True)\n    \n    # merge labels and helmets using 'datetime_ngs'\n    play_data = helmets.merge(labels.query('contact==1')[['datetime_ngs', 'nfl_player_id_1', 'nfl_player_id_2', 'contact_id']],\n                             left_on = ['datetime_ngs', 'nfl_player_id'],\n                             right_on = ['datetime_ngs', 'nfl_player_id_1'],\n                             how = 'left')\n    \n    return play_data","metadata":{"execution":{"iopub.status.busy":"2023-01-20T08:35:17.079311Z","iopub.execute_input":"2023-01-20T08:35:17.079782Z","iopub.status.idle":"2023-01-20T08:35:17.090474Z","shell.execute_reply.started":"2023-01-20T08:35:17.079744Z","shell.execute_reply":"2023-01-20T08:35:17.089464Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def annotate_video_with_contact_labels(video_path: str, baseline_boxes:pd.DataFrame, verbose=True) -> str:\n    \"\"\"\n    Annotates a video with baseline model boxes and labels.\n    Helmet boxes are colored based on the contact label.\n    \"\"\"\n    video_name = video_path.split('/')[-1]\n    play_name = video_name.split('.')[0]\n    play_name = '_'.join(play_name.split('_')[:-1])\n    HELMET_COLOR = (0, 0, 0) # black\n    baseline_boxes = baseline_boxes.query(\"game_play==@play_name\").copy()\n    \n    # verbose\n    if verbose==True:\n        print(f\"Running for {video_name}\")\n    \n    # VideoCapture Object\n    cap  = cv2.VideoCapture(video_path)\n    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n    fps = cap.get(cv2.CAP_PROP_FPS)\n    \n    # VideoWriter Object\n    output_path = \"labeled_\" + video_name\n    tmp_output_path = \"tmp_\" + output_path\n    out = cv2.VideoWriter(tmp_output_path, cv2.VideoWriter_fourcc(*'MP4V'), \n                          fps, (width, height))\n    \n    # check  if camera opened successfully\n    if cap.isOpened() == False:\n        print('Error opening file')\n        \n    # read until video is complete\n    frame  = 1\n    while(cap.isOpened()):\n        # capture frame by frame\n        ret, img = cap.read()\n        \n        if ret==True:\n            # add play_name text\n            cv2.putText(img, play_name,\n                       (int(0.01*width), int(0.05*height)),\n                        cv2.FONT_HERSHEY_SIMPLEX,\n                        1,\n                        HELMET_COLOR,\n                        1\n                       )\n            \n            # add frame counter\n            cv2.putText(img, \"Frame: \"+str(frame),\n                       (int(0.75*width), int(0.05*height)),\n                        cv2.FONT_HERSHEY_SIMPLEX,\n                        1,\n                        HELMET_COLOR,\n                        1\n                       )\n\n            # adding helmet bounding boxes, player tags and contact labels\n            bbox_set = baseline_boxes.query(\"frame==@frame\")\n             # ensuring null values (no contact) come first, the player-player contact and then player-ground contact\n            bbox_set = bbox_set.sort_values(by='nfl_player_id_2', na_position='first', ascending=True)\n            for idx, row in bbox_set.iterrows():\n                if pd.isnull(row.contact_id):\n                    # add black (no contact) helmet bbox\n                    cv2.rectangle(img, \n                                  (int(row.left),int(row.top)), \n                                  (int(row.left+row.width), int(row.top+row.height)),\n                                  HELMET_COLOR, # black\n                                  1\n                                 )\n                else:\n                    if row.nfl_player_id_2=='G':\n                        # add red (contact with ground)helmet bbox\n                        cv2.rectangle(img, \n                                      (int(row.left),int(row.top)), \n                                      (int(row.left+row.width), int(row.top+row.height)),\n                                      (0, 0, 225), # red\n                                      2\n                                     )\n                    else:\n                        # add green (contact with another player) helmet bbox\n                        cv2.rectangle(img, \n                                      (int(row.left),int(row.top)), \n                                      (int(row.left+row.width), int(row.top+row.height)),\n                                      (0, 255, 0), # green\n                                      2\n                                     )\n                        \n                        # check if player 2 in view\n                        row_player_2 = bbox_set.query('nfl_player_id_2==@row.nfl_player_id_2')\n                        if len(row_player_2)==0:\n                            pass\n                        else:\n                            # add green (contact with another player) helmet bbox (for player 2)\n                            cv2.rectangle(img, \n                                          (int(row_player_2.left.values[0]),int(row_player_2.top.values[0])), \n                                          (int(row_player_2.left.values[0]+row_player_2.width.values[0]), int(row_player_2.top.values[0]+row_player_2.height.values[0])),\n                                          (0, 255, 0), # green\n                                          2\n                                         )\n                            # add blue connecting line between players in contact\n                            cv2.line(img,\n                                    (int(row.left+(row.width/2)), int(row.top+(row.height/2))),\n                                    (int(row_player_2.left.values[0]+(row_player_2.width.values[0]/2)), int(row_player_2.top.values[0]+(row_player_2.height.values[0]/2))),\n                                    (255, 0, 0), # blue\n                                    2\n                                    )\n                        \n                # add player label\n                cv2.putText(img, row.player_label,\n                        (int(row.left+10), int(row.top+10)),\n                        cv2.FONT_HERSHEY_SIMPLEX,\n                        1,\n                        HELMET_COLOR,\n                        1\n                        )\n                \n                \n            frame += 1\n            out.write(img) \n                \n        # break the loop   \n        else: \n            break\n        \n        \n    out.release()\n    \n    # Not all browsers support the codec, we will re-load the file at tmp_output_path\n    # and convert to a codec that is more broadly readable using ffmpeg\n    if os.path.exists(output_path):\n        os.remove(output_path)\n    subprocess.run(\n        [\n            \"ffmpeg\",\n            \"-i\",\n            tmp_output_path,\n            \"-crf\",\n            \"18\",\n            \"-preset\",\n            \"veryfast\",\n            \"-hide_banner\",\n            \"-loglevel\",\n            \"error\",\n            \"-vcodec\",\n            \"libx264\",\n            output_path,\n        ]\n    )\n    os.remove(tmp_output_path)\n    \n    return output_path","metadata":{"execution":{"iopub.status.busy":"2023-01-20T09:03:54.458558Z","iopub.execute_input":"2023-01-20T09:03:54.458998Z","iopub.status.idle":"2023-01-20T09:03:54.489900Z","shell.execute_reply.started":"2023-01-20T09:03:54.458964Z","shell.execute_reply":"2023-01-20T09:03:54.488582Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"video_path = '/kaggle/input/nfl-player-contact-detection/train/58168_003392_Endzone.mp4'\nplay_name = '58168_003392'\nview = 'Endzone'","metadata":{"execution":{"iopub.status.busy":"2023-01-20T09:05:19.829696Z","iopub.execute_input":"2023-01-20T09:05:19.830163Z","iopub.status.idle":"2023-01-20T09:05:19.836126Z","shell.execute_reply.started":"2023-01-20T09:05:19.830123Z","shell.execute_reply":"2023-01-20T09:05:19.834956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gp = join_helmets_contact(play_name, train_labels_data, train_baseline_helmets_data, train_video_metadata, view=view)\npath = annotate_video_with_contact_labels(video_path, gp)\nplay_video(path)","metadata":{"execution":{"iopub.status.busy":"2023-01-20T09:06:23.982404Z","iopub.execute_input":"2023-01-20T09:06:23.982860Z"},"trusted":true},"execution_count":null,"outputs":[]}]}