{"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":"# NFL - Player Contact Detection Challenge\n\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F644036%2F65cd663d2c823043b36ecda6c93c1304%2Fcontact-example.gif?generation=1670265252697886&alt=media\" style=\"display:block; margin: 20px auto;\">\n\n\nIn this competition, you are tasked with predicting moments of contact between player pairs, as well as when players make non-foot contact with the ground using game footage and tracking data. Each play has four associated videos. Two videos, showing a sideline and endzone view, are time synced and aligned with each other. Additionally, an All29 view is provided but not guaranteed to be time synced. The training set videos are in **train/** with corresponding labels in **train_labels.csv**, while the videos for which you must predict are in the **test/** folder.\n\nThis year we are also providing baseline helmet detection and assignment boxes for the training and test set. **train_baseline_helmets.csv** is the output from last year's winning player assignment model.\n\n**train_player_tracking.csv** provides 10 Hz tracking data for each player on the field during the provided plays.\n\n**train_video_metadata.csv** contains timestamps associated with each Sideline and Endzone view for syncing with the player tracking data.\n\nThis is a code competition. When you submit, your model will be rerun on a set of 61 unseen plays located in a holdout test set. The publicly provided test videos are simply a set of mock plays (copied from the training set) which are not used in scoring.\n\nThe associated **test_baseline_helmets.csv**, **test_player_tracking.csv**, and **test_video_metadata.csv** are available to your model when submitting.\n\nA **sample_submission.csv** will be available when submitting and will contain all rows required for a valid submission.\n","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","papermill":{"duration":0.009872,"end_time":"2022-12-05T18:51:57.963249","exception":false,"start_time":"2022-12-05T18:51:57.953377","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pylab as plt\n\nfrom sklearn.metrics import matthews_corrcoef\n\n# Read in data files\nBASE_DIR = \"../input/nfl-player-contact-detection\"\n\n# Labels and sample submission\nlabels = pd.read_csv(f\"{BASE_DIR}/train_labels.csv\", parse_dates=[\"datetime\"])\n\nss = pd.read_csv(f\"{BASE_DIR}/sample_submission.csv\")\n\n# Player tracking data\ntr_tracking = pd.read_csv(\n    f\"{BASE_DIR}/train_player_tracking.csv\", parse_dates=[\"datetime\"]\n)\nte_tracking = pd.read_csv(\n    f\"{BASE_DIR}/test_player_tracking.csv\", parse_dates=[\"datetime\"]\n)\n\n# Baseline helmet detection labels\ntr_helmets = pd.read_csv(f\"{BASE_DIR}/train_baseline_helmets.csv\")\nte_helmets = pd.read_csv(f\"{BASE_DIR}/test_baseline_helmets.csv\")\n\n# Video metadata with start/stop timestamps\ntr_video_metadata = pd.read_csv(\n    \"../input/nfl-player-contact-detection/train_video_metadata.csv\",\n    parse_dates=[\"start_time\", \"end_time\", \"snap_time\"],\n)\n","metadata":{"papermill":{"duration":38.158339,"end_time":"2022-12-05T18:52:36.128186","exception":false,"start_time":"2022-12-05T18:51:57.969847","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:09:30.245311Z","iopub.execute_input":"2022-12-05T20:09:30.245764Z","iopub.status.idle":"2022-12-05T20:10:16.850799Z","shell.execute_reply.started":"2022-12-05T20:09:30.245711Z","shell.execute_reply":"2022-12-05T20:10:16.849159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# What is the goal of this competition?\nSimply put, we are trying to predict all moments of contact that occurs for each 0.1 second within a football play. Contact can occur in two ways:\n- Between two players.\n- Between a player and the ground. **Note: A player is considered in contact with the ground when he touches the ground with anything other than his hands or his feet.**\n\nSubmissions should contain predictions for all possible combination of the 22 players, and each player with the ground for each timestep within the play. Only one row is required for each predicted pair, with the lower player's `nfl_player_id` as `nfl_player_id_1`  and the larger as `nfl_player_id_2`.","metadata":{"papermill":{"duration":0.005947,"end_time":"2022-12-05T18:52:36.140921","exception":false,"start_time":"2022-12-05T18:52:36.134974","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# What Data are Provided?\n\nFor the full data details, please review the data description page.\n\n*Note: This is a code competition. When you submit, your model will be rerun on a set of 60 unseen plays located in a holdout test set. The publicly provided test videos are simply a set of mock plays (copied from the training set) which are not used in scoring.*","metadata":{"papermill":{"duration":0.006102,"end_time":"2022-12-05T18:52:36.154010","exception":false,"start_time":"2022-12-05T18:52:36.147908","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# Video and Baseline Boxes\nAs noted above, the provided baseline boxes are imperfect but allow you to quickly tackle the problem without having to address the helmet detection. The below video shows an example of these baseline predictions alongside the true helmet labels.","metadata":{"papermill":{"duration":0.005743,"end_time":"2022-12-05T18:52:36.165905","exception":false,"start_time":"2022-12-05T18:52:36.160162","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nimport cv2\nimport subprocess\nfrom IPython.display import Video, display\nimport pandas as pd\n\n\ndef video_with_helmets(\n    video_path: str, baseline_boxes: pd.DataFrame, verbose=True\n) -> str:\n    \"\"\"\n    Annotates a video with baseline model boxes and labels.\n    \"\"\"\n    VIDEO_CODEC = \"MP4V\"\n    HELMET_COLOR = (0, 0, 0)  # Black\n    video_name = os.path.basename(video_path)\n    if verbose:\n        print(f\"Running for {video_name}\")\n    baseline_boxes = baseline_boxes.copy()\n\n    vidcap = cv2.VideoCapture(video_path)\n    fps = vidcap.get(cv2.CAP_PROP_FPS)\n    width = int(vidcap.get(cv2.CAP_PROP_FRAME_WIDTH))\n    height = int(vidcap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n    output_path = \"labeled_\" + video_name\n    tmp_output_path = \"tmp_\" + output_path\n    output_video = cv2.VideoWriter(\n        tmp_output_path, cv2.VideoWriter_fourcc(*VIDEO_CODEC), fps, (width, height)\n    )\n\n    frame = 0\n    while True:\n        it_worked, img = vidcap.read()\n        if not it_worked:\n            break\n        # We need to add 1 to the frame count to match the label frame index\n        # that starts at 1\n        frame += 1\n\n        # Let's add a frame index to the video so we can track where we are\n        img_name = video_name.replace(\".mp4\", \"\")\n        cv2.putText(\n            img,\n            img_name,\n            (10, 30),\n            cv2.FONT_HERSHEY_SIMPLEX,\n            1,\n            HELMET_COLOR,\n            thickness=1,\n        )\n\n        cv2.putText(\n            img,\n            str(frame),\n            (1280 - 90, 720 - 20),\n            cv2.FONT_HERSHEY_SIMPLEX,\n            1,\n            HELMET_COLOR,\n            thickness=1,\n        )\n\n        # Now, add the boxes\n        boxes = baseline_boxes.query(\"video == @video_name and frame == @frame\")\n        for box in boxes.itertuples(index=False):\n            cv2.rectangle(\n                img,\n                (box.left, box.top),\n                (box.left + box.width, box.top + box.height),\n                HELMET_COLOR,\n                thickness=1,\n            )\n            cv2.putText(\n                img,\n                box.player_label,\n                (box.left + 1, max(0, box.top - 20)),\n                cv2.FONT_HERSHEY_SIMPLEX,\n                0.5,\n                HELMET_COLOR,\n                thickness=1,\n            )\n\n        output_video.write(img)\n    output_video.release()\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\n","metadata":{"_kg_hide-input":true,"papermill":{"duration":0.275206,"end_time":"2022-12-05T18:52:36.447280","exception":false,"start_time":"2022-12-05T18:52:36.172074","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:10:24.861993Z","iopub.execute_input":"2022-12-05T20:10:24.862375Z","iopub.status.idle":"2022-12-05T20:10:24.878449Z","shell.execute_reply.started":"2022-12-05T20:10:24.862343Z","shell.execute_reply":"2022-12-05T20:10:24.877416Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This video displays these helmet boxes on an example play. They are provided starting a few frames prior to the play starting and the video displays the associated player label (home/visiting team in combination with jersey number) next to the helmet box.","metadata":{"papermill":{"duration":0.007113,"end_time":"2022-12-05T18:52:36.460745","exception":false,"start_time":"2022-12-05T18:52:36.453632","status":"completed"},"tags":[]}},{"cell_type":"code","source":"example_video = \"../input/nfl-player-contact-detection/train/58168_003392_Sideline.mp4\"\noutput_video = video_with_helmets(example_video, tr_helmets)\n\nfrac = 0.65  # scaling factor for display\ndisplay(\n    Video(data=output_video, embed=True, height=int(720 * frac), width=int(1280 * frac))\n)\n","metadata":{"papermill":{"duration":68.001796,"end_time":"2022-12-05T18:53:44.468436","exception":false,"start_time":"2022-12-05T18:52:36.466640","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:10:26.550031Z","iopub.execute_input":"2022-12-05T20:10:26.550451Z","iopub.status.idle":"2022-12-05T20:11:52.044669Z","shell.execute_reply.started":"2022-12-05T20:10:26.550415Z","shell.execute_reply":"2022-12-05T20:11:52.041467Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# NGS Tracking Data\nThe use of the NGS tracking data will be important for correctly labeling videos. Some things to note are:\n\n- NGS data is sampled at a rate of 10Hz, while videos are sampled at roughly 59.94Hz.\n- The tracking data can be approximately synced with video using the training metadata.\n- NGS data contains a `step` column which can we used to combine with the labels and sample submission file.\n- Features including speed, acceleration, orientation are included. For a full list refer to the data description page.\n","metadata":{"papermill":{"duration":0.127577,"end_time":"2022-12-05T18:53:44.725271","exception":false,"start_time":"2022-12-05T18:53:44.597694","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import matplotlib.patches as patches\nimport matplotlib.pylab as plt\n\ndef create_football_field(\n    linenumbers=True,\n    endzones=True,\n    figsize=(12, 6.33),\n    line_color=\"black\",\n    field_color=\"white\",\n    ez_color=None,\n    ax=None,\n    return_fig=False,\n):\n    \"\"\"\n    Function that plots the football field for viewing plays.\n    Allows for showing or hiding endzones.\n    \"\"\"\n\n    if ez_color is None:\n        ez_color = field_color\n\n    rect = patches.Rectangle(\n        (0, 0),\n        120,\n        53.3,\n        linewidth=0.1,\n        edgecolor=\"r\",\n        facecolor=field_color,\n        zorder=0,\n    )\n\n    if ax is None:\n        fig, ax = plt.subplots(1, figsize=figsize)\n    ax.add_patch(rect)\n    ax.plot([10, 10, 10, 20, 20, 30, 30, 40, 40, 50, 50, 60, 60, 70, 70, 80,\n              80, 90, 90, 100, 100, 110, 110, 120, 0, 0, 120, 120],\n             [0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3,\n              53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 53.3, 0, 0, 53.3],\n             color=line_color)\n    \n    # Endzones\n    if endzones:\n        ez1 = patches.Rectangle(\n            (0, 0),\n            10,\n            53.3,\n            linewidth=0.1,\n            edgecolor=line_color,\n            facecolor=ez_color,\n            alpha=0.6,\n            zorder=0,\n        )\n        ez2 = patches.Rectangle(\n            (110, 0),\n            10,\n            53.3,\n            linewidth=0.1,\n            edgecolor=line_color,\n            facecolor=ez_color,\n            alpha=0.6,\n            zorder=0,\n        )\n        ax.add_patch(ez1)\n        ax.add_patch(ez2)\n\n    ax.axis(\"off\")\n    if linenumbers:\n        for x in range(20, 110, 10):\n            numb = x\n            if x > 50:\n                numb = 120 - x\n            ax.text(\n                x,\n                5,\n                str(numb - 10),\n                horizontalalignment=\"center\",\n                fontsize=20,  # fontname='Arial',\n                color=line_color,\n            )\n            ax.text(\n                x - 0.95,\n                53.3 - 5,\n                str(numb - 10),\n                horizontalalignment=\"center\",\n                fontsize=20,  # fontname='Arial',\n                color=line_color,\n                rotation=180,\n            )\n    if endzones:\n        hash_range = range(11, 110)\n    else:\n        hash_range = range(1, 120)\n\n    for x in hash_range:\n        ax.plot([x, x], [0.4, 0.7], color=line_color)\n        ax.plot([x, x], [53.0, 52.5], color=line_color)\n        ax.plot([x, x], [22.91, 23.57], color=line_color)\n        ax.plot([x, x], [29.73, 30.39], color=line_color)\n\n    border = patches.Rectangle(\n        (-5, -5),\n        120 + 10,\n        53.3 + 10,\n        linewidth=0.1,\n        edgecolor=\"orange\",\n        facecolor=line_color,\n        alpha=0,\n        zorder=0,\n    )\n    ax.add_patch(border)\n    ax.set_xlim((-5, 125))\n    ax.set_ylim((-5, 53.3 + 5))\n\n    if return_fig:\n        return fig, ax\n    else:\n        return ax","metadata":{"_kg_hide-input":true,"papermill":{"duration":0.148632,"end_time":"2022-12-05T18:53:45.003197","exception":false,"start_time":"2022-12-05T18:53:44.854565","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:00.554810Z","iopub.execute_input":"2022-12-05T20:13:00.555306Z","iopub.status.idle":"2022-12-05T20:13:00.582692Z","shell.execute_reply.started":"2022-12-05T20:13:00.555269Z","shell.execute_reply":"2022-12-05T20:13:00.581538Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"game_play = \"58168_003392\"\nexample_tracks = tr_tracking.query(\"game_play == @game_play and step == 0\")\nax = create_football_field()\nfor team, d in example_tracks.groupby(\"team\"):\n    ax.scatter(\n        d[\"x_position\"],\n        d[\"y_position\"],\n        label=team,\n        s=65,\n        lw=1,\n        edgecolors=\"black\",\n        zorder=5,\n    )\nax.legend().remove()\nax.set_title(f\"Tracking data for {game_play}: at step 0\", fontsize=15)\nplt.show()\n","metadata":{"papermill":{"duration":0.712448,"end_time":"2022-12-05T18:53:45.841767","exception":false,"start_time":"2022-12-05T18:53:45.129319","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:01.338812Z","iopub.execute_input":"2022-12-05T20:13:01.339218Z","iopub.status.idle":"2022-12-05T20:13:02.046268Z","shell.execute_reply.started":"2022-12-05T20:13:01.339186Z","shell.execute_reply":"2022-12-05T20:13:02.044687Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tr_tracking.head()","metadata":{"execution":{"iopub.status.busy":"2022-12-05T20:13:04.299405Z","iopub.execute_input":"2022-12-05T20:13:04.300433Z","iopub.status.idle":"2022-12-05T20:13:04.326691Z","shell.execute_reply.started":"2022-12-05T20:13:04.300374Z","shell.execute_reply":"2022-12-05T20:13:04.325011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Video Metadata\n\nThese files provide information that can be used to sync the video files with the NGS tracking data.","metadata":{"papermill":{"duration":0.127239,"end_time":"2022-12-05T18:53:46.099423","exception":false,"start_time":"2022-12-05T18:53:45.972184","status":"completed"},"tags":[]}},{"cell_type":"code","source":"tr_video_metadata.head()","metadata":{"papermill":{"duration":0.147403,"end_time":"2022-12-05T18:53:46.374671","exception":false,"start_time":"2022-12-05T18:53:46.227268","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:04.957899Z","iopub.execute_input":"2022-12-05T20:13:04.958804Z","iopub.status.idle":"2022-12-05T20:13:04.979924Z","shell.execute_reply.started":"2022-12-05T20:13:04.958755Z","shell.execute_reply":"2022-12-05T20:13:04.978849Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Contact Labels\n\nContact labels identify moments when two players are in contact or in contact with the ground. Some things to note about the labels:\n- Labels are provided at 10Hz, synchronized with the NGS tracking data.\n- Labels start at step == 0 (play start), and end at the step associated with the shortest video (Sideline or Endzone)\n- Labels (and submissions) will have a single row for each:\n    - Player pair combination where nfl_player_id_1 < nfl_player_id_2\n    - Player to ground where nfl_player_id == \"G\"\n- The `contact_id` column is a unique identifier. It is a combination of the `game_play`, `step`, `nfl_player_id_1` and `nfl_player_id_2` columns.\n- The `contact` column is a binary value where 0 indicates no-contact and 1 indicates contact.\n\nEven though the labels and video are sampled at different rates, we can approximately join them so that we can visualize them with the video. Below we've provided an example function for joining.\n","metadata":{"papermill":{"duration":0.120231,"end_time":"2022-12-05T18:53:46.617340","exception":false,"start_time":"2022-12-05T18:53:46.497109","status":"completed"},"tags":[]}},{"cell_type":"code","source":"labels.head()","metadata":{"papermill":{"duration":0.141034,"end_time":"2022-12-05T18:53:46.885627","exception":false,"start_time":"2022-12-05T18:53:46.744593","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:05.596572Z","iopub.execute_input":"2022-12-05T20:13:05.597421Z","iopub.status.idle":"2022-12-05T20:13:05.612446Z","shell.execute_reply.started":"2022-12-05T20:13:05.597380Z","shell.execute_reply":"2022-12-05T20:13:05.611113Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def join_helmets_contact(game_play, labels, helmets, meta, view=\"Sideline\", fps=59.94):\n    \"\"\"\n    Joins helmets and labels for a given game_play. Results can be used for visualizing labels.\n    Returns a dataframe with the joint dataframe, duplicating rows if multiple contacts occur.\n    \"\"\"\n    gp_labs = labels.query(\"game_play == @game_play\").copy()\n    gp_helms = helmets.query(\"game_play == @game_play\").copy()\n\n    start_time = meta.query(\"game_play == @game_play and view == @view\")[\n        \"start_time\"\n    ].values[0]\n\n    gp_helms[\"datetime\"] = (\n        pd.to_timedelta(gp_helms[\"frame\"] * (1 / fps), unit=\"s\") + start_time\n    )\n    gp_helms[\"datetime\"] = pd.to_datetime(gp_helms[\"datetime\"], utc=True)\n    gp_helms[\"datetime_ngs\"] = (\n        pd.DatetimeIndex(gp_helms[\"datetime\"] + pd.to_timedelta(50, \"ms\"))\n        .floor(\"100ms\")\n        .values\n    )\n    gp_helms[\"datetime_ngs\"] = pd.to_datetime(gp_helms[\"datetime_ngs\"], utc=True)\n\n    gp_labs[\"datetime_ngs\"] = pd.to_datetime(gp_labs[\"datetime\"], utc=True)\n\n    gp = gp_helms.merge(\n        gp_labs.query(\"contact == 1\")[\n            [\"datetime_ngs\", \"nfl_player_id_1\", \"nfl_player_id_2\", \"contact_id\"]\n        ],\n        left_on=[\"datetime_ngs\", \"nfl_player_id\"],\n        right_on=[\"datetime_ngs\", \"nfl_player_id_1\"],\n        how=\"left\",\n    )\n    return gp\n","metadata":{"_kg_hide-input":true,"papermill":{"duration":0.138777,"end_time":"2022-12-05T18:53:47.152306","exception":false,"start_time":"2022-12-05T18:53:47.013529","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:05.911425Z","iopub.execute_input":"2022-12-05T20:13:05.912336Z","iopub.status.idle":"2022-12-05T20:13:05.926329Z","shell.execute_reply.started":"2022-12-05T20:13:05.912287Z","shell.execute_reply":"2022-12-05T20:13:05.924484Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport cv2\nimport subprocess\nfrom IPython.display import Video, display\nimport pandas as pd\n\n\ndef video_with_contact(\n    video_path: str, baseline_boxes: pd.DataFrame, verbose=True\n) -> str:\n    \"\"\"\n    Annotates a video with baseline model boxes.\n    Helmet boxes are colored based on the contact label.\n    \"\"\"\n    VIDEO_CODEC = \"MP4V\"\n    HELMET_COLOR = (0, 0, 0)  # Black\n    video_name = os.path.basename(video_path)\n    if verbose:\n        print(f\"Running for {video_name}\")\n    baseline_boxes = baseline_boxes.copy()\n\n    vidcap = cv2.VideoCapture(video_path)\n    fps = vidcap.get(cv2.CAP_PROP_FPS)\n    width = int(vidcap.get(cv2.CAP_PROP_FRAME_WIDTH))\n    height = int(vidcap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n    output_path = \"contact_\" + video_name\n    tmp_output_path = \"tmp_\" + output_path\n    output_video = cv2.VideoWriter(\n        tmp_output_path, cv2.VideoWriter_fourcc(*VIDEO_CODEC), fps, (width, height)\n    )\n    frame = 0\n    while True:\n        it_worked, img = vidcap.read()\n        if not it_worked:\n            break\n        # We need to add 1 to the frame count to match the label frame index\n        # that starts at 1\n        frame += 1\n\n        # Let's add a frame index to the video so we can track where we are\n        img_name = video_name.replace('.mp4','')\n        cv2.putText(\n            img,\n            img_name,\n            (10, 30),\n            cv2.FONT_HERSHEY_SIMPLEX,\n            1,\n            HELMET_COLOR,\n            thickness=1,\n        )\n        \n        cv2.putText(\n            img,\n            str(frame),\n            (1280 - 90, 720 - 20),\n            cv2.FONT_HERSHEY_SIMPLEX,\n            1,\n            HELMET_COLOR,\n            thickness=1,\n        )\n\n        # Now, add the boxes\n        boxes = baseline_boxes.query(\"video == @video_name and frame == @frame\")\n        contact_players = boxes.dropna(subset=[\"nfl_player_id_2\"]).query(\n            'nfl_player_id_2 != \"G\"'\n        )\n        contact_ids = (\n            contact_players[\"nfl_player_id_1\"].astype(\"int\").values.tolist()\n            + contact_players[\"nfl_player_id_2\"].astype(\"int\").values.tolist()\n        )\n        for box in boxes.itertuples(index=False):\n\n            if box.nfl_player_id_2 == \"G\":\n                box_color = (0, 0, 255)  # Red\n                box_thickness = 2\n            elif int(box.nfl_player_id) in contact_ids:\n                box_color = (0, 255, 0)  # green\n                box_thickness = 2\n\n                # Add line between players in contact\n                if not np.isnan(float(box.nfl_player_id_2)):\n                    player2 = int(box.nfl_player_id_2)\n                    player2_row = boxes.query(\"nfl_player_id == @player2\")\n                    if len(player2_row) == 0:\n                        # Player 2 is not in view\n                        continue\n                    cv2.line(\n                        img,\n                        (box.left + int(box.width / 2), box.top + int(box.height / 2)),\n                        (\n                            player2_row.left.values[0]\n                            + int(player2_row.width.values[0] / 2),\n                            player2_row.top.values[0]\n                            + int(player2_row.height.values[0] / 2),\n                        ),\n                        color=(255, 0, 0),\n                        thickness=2,\n                    )\n\n            else:\n                box_color = HELMET_COLOR\n                box_thickness = 1\n\n            # Draw lines between two boxes\n\n            cv2.rectangle(\n                img,\n                (box.left, box.top),\n                (box.left + box.width, box.top + box.height),\n                box_color,\n                thickness=box_thickness,\n            )\n            cv2.putText(\n                img,\n                box.player_label,\n                (box.left + 1, max(0, box.top - 20)),\n                cv2.FONT_HERSHEY_SIMPLEX,\n                0.5,\n                HELMET_COLOR,\n                thickness=1,\n            )\n\n        output_video.write(img)\n    output_video.release()\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\n","metadata":{"_kg_hide-input":true,"papermill":{"duration":0.149334,"end_time":"2022-12-05T18:53:47.421509","exception":false,"start_time":"2022-12-05T18:53:47.272175","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:06.109361Z","iopub.execute_input":"2022-12-05T20:13:06.109822Z","iopub.status.idle":"2022-12-05T20:13:06.139322Z","shell.execute_reply.started":"2022-12-05T20:13:06.109785Z","shell.execute_reply":"2022-12-05T20:13:06.137087Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Example Video with Labels\n\nIn this video you can see the labels for this play, approximately linked with the associated helmets within the video.\n\nNote in this video:\n- **Black** helmet boxes indicate that player is not in contact. A unique number (home/visiting combined with jersey number) is shown next to their helmet.\n- **Green** helmet box indicates the player is in contact with one or more players.\n- **Red** helmet box indicates 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":{"papermill":{"duration":0.125008,"end_time":"2022-12-05T18:53:47.672104","exception":false,"start_time":"2022-12-05T18:53:47.547096","status":"completed"},"tags":[]}},{"cell_type":"code","source":"game_play = \"58168_003392\"\ngp = join_helmets_contact(game_play, labels, tr_helmets, tr_video_metadata)\n\nexample_video = f\"../input/nfl-player-contact-detection/train/{game_play}_Sideline.mp4\"\noutput_video = video_with_contact(example_video, gp)\n\nfrac = 0.65  # scaling factor for display\ndisplay(\n    Video(data=output_video, embed=True, height=int(720 * frac), width=int(1280 * frac))\n)\n","metadata":{"papermill":{"duration":21.040193,"end_time":"2022-12-05T18:54:08.837849","exception":false,"start_time":"2022-12-05T18:53:47.797656","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:06.483331Z","iopub.execute_input":"2022-12-05T20:13:06.485372Z","iopub.status.idle":"2022-12-05T20:13:37.003152Z","shell.execute_reply.started":"2022-12-05T20:13:06.485296Z","shell.execute_reply":"2022-12-05T20:13:37.000938Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Sample Submission\n\nA **sample_submission.csv** file is provided. When your notebook is being processed on the test set this file will include every row required for a valid submission.\n\nNote the `contact_id` column is a unique identifier consisting of a combination of `game_play`, `step`, `nfl_player_id_1` and `nfl_player_id_2`","metadata":{"papermill":{"duration":0.244177,"end_time":"2022-12-05T18:54:09.341272","exception":false,"start_time":"2022-12-05T18:54:09.097095","status":"completed"},"tags":[]}},{"cell_type":"code","source":"ss = pd.read_csv(f'{BASE_DIR}/sample_submission.csv')\nss.head()","metadata":{"papermill":{"duration":0.292185,"end_time":"2022-12-05T18:54:09.883735","exception":false,"start_time":"2022-12-05T18:54:09.591550","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:37.007571Z","iopub.execute_input":"2022-12-05T20:13:37.010652Z","iopub.status.idle":"2022-12-05T20:13:37.084585Z","shell.execute_reply.started":"2022-12-05T20:13:37.010545Z","shell.execute_reply":"2022-12-05T20:13:37.082988Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Baseline Approach\n\nWe can create a simple solution using only the tracking data and identifying an optimal threshold based on the training data for the competition metric.\n\nThis baseline submission does not consider player to ground contact, and predicts every player-to-ground row as non-contact.\n\nThe process is simple:\n- For each `contact_id` we compute the seperation distance between players.\n- We fill player-to-ground rows with a distance of 99 so they are treated as non-contact.\n- We loop through thresholds between 0 and 5 yards and compute the competition metric at each threshold.\n- We use the threshold that produces the best score when applying this to our test submission.","metadata":{"papermill":{"duration":0.233183,"end_time":"2022-12-05T18:54:10.355407","exception":false,"start_time":"2022-12-05T18:54:10.122224","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def compute_distance(df, tr_tracking, merge_col=\"datetime\"):\n    \"\"\"\n    Merges tracking data on player1 and 2 and computes the distance.\n    \"\"\"\n    df_combo = (\n        df.astype({\"nfl_player_id_1\": \"str\"})\n        .merge(\n            tr_tracking.astype({\"nfl_player_id\": \"str\"})[\n                [\"game_play\", merge_col, \"nfl_player_id\", \"x_position\", \"y_position\"]\n            ],\n            left_on=[\"game_play\", merge_col, \"nfl_player_id_1\"],\n            right_on=[\"game_play\", merge_col, \"nfl_player_id\"],\n            how=\"left\",\n        )\n        .rename(columns={\"x_position\": \"x_position_1\", \"y_position\": \"y_position_1\"})\n        .drop(\"nfl_player_id\", axis=1)\n        .merge(\n            tr_tracking.astype({\"nfl_player_id\": \"str\"})[\n                [\"game_play\", merge_col, \"nfl_player_id\", \"x_position\", \"y_position\"]\n            ],\n            left_on=[\"game_play\", merge_col, \"nfl_player_id_2\"],\n            right_on=[\"game_play\", merge_col, \"nfl_player_id\"],\n            how=\"left\",\n        )\n        .drop(\"nfl_player_id\", axis=1)\n        .rename(columns={\"x_position\": \"x_position_2\", \"y_position\": \"y_position_2\"})\n        .copy()\n    )\n\n    df_combo[\"distance\"] = np.sqrt(\n        np.square(df_combo[\"x_position_1\"] - df_combo[\"x_position_2\"])\n        + np.square(df_combo[\"y_position_1\"] - df_combo[\"y_position_2\"])\n    )\n    return df_combo\n\n\ndef add_contact_id(df):\n    # Create contact ids\n    df[\"contact_id\"] = (\n        df[\"game_play\"]\n        + \"_\"\n        + df[\"step\"].astype(\"str\")\n        + \"_\"\n        + df[\"nfl_player_id_1\"].astype(\"str\")\n        + \"_\"\n        + df[\"nfl_player_id_2\"].astype(\"str\")\n    )\n    return df\n\n\ndef expand_contact_id(df):\n    \"\"\"\n    Splits out contact_id into seperate columns.\n    \"\"\"\n    df[\"game_play\"] = df[\"contact_id\"].str[:12]\n    df[\"step\"] = df[\"contact_id\"].str.split(\"_\").str[-3].astype(\"int\")\n    df[\"nfl_player_id_1\"] = df[\"contact_id\"].str.split(\"_\").str[-2]\n    df[\"nfl_player_id_2\"] = df[\"contact_id\"].str.split(\"_\").str[-1]\n    return df\n","metadata":{"_kg_hide-input":true,"papermill":{"duration":0.253196,"end_time":"2022-12-05T18:54:10.846492","exception":false,"start_time":"2022-12-05T18:54:10.593296","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:37.086455Z","iopub.execute_input":"2022-12-05T20:13:37.086923Z","iopub.status.idle":"2022-12-05T20:13:37.101388Z","shell.execute_reply.started":"2022-12-05T20:13:37.086887Z","shell.execute_reply":"2022-12-05T20:13:37.100182Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_combo = compute_distance(labels, tr_tracking)\n\nprint(df_combo.shape, labels.shape)\n\ndf_dist = df_combo.merge(\n    tr_tracking[[\"game_play\", \"datetime\", \"step\"]].drop_duplicates()\n)\ndf_dist[\"distance\"] = df_dist[\"distance\"].fillna(99)  # Fill player to ground with 99\n","metadata":{"papermill":{"duration":13.094965,"end_time":"2022-12-05T18:54:24.177409","exception":false,"start_time":"2022-12-05T18:54:11.082444","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:37.104054Z","iopub.execute_input":"2022-12-05T20:13:37.104444Z","iopub.status.idle":"2022-12-05T20:13:58.512414Z","shell.execute_reply.started":"2022-12-05T20:13:37.104412Z","shell.execute_reply":"2022-12-05T20:13:58.511428Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Note our dataframe now includes `distance`.","metadata":{"papermill":{"duration":0.24389,"end_time":"2022-12-05T18:54:24.685485","exception":false,"start_time":"2022-12-05T18:54:24.441595","status":"completed"},"tags":[]}},{"cell_type":"code","source":"df_dist.head()","metadata":{"papermill":{"duration":0.255498,"end_time":"2022-12-05T18:54:25.187788","exception":false,"start_time":"2022-12-05T18:54:24.932290","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:58.514215Z","iopub.execute_input":"2022-12-05T20:13:58.514916Z","iopub.status.idle":"2022-12-05T20:13:58.537504Z","shell.execute_reply.started":"2022-12-05T20:13:58.514870Z","shell.execute_reply":"2022-12-05T20:13:58.535771Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for dist in range(0, 5):\n    score = matthews_corrcoef(df_dist[\"contact\"], df_dist[\"distance\"] <= dist)\n    print(f\"Threshold Distance: {dist} Yard - MCC Score: {score:0.4f}\")\n","metadata":{"papermill":{"duration":5.708536,"end_time":"2022-12-05T18:54:31.132842","exception":false,"start_time":"2022-12-05T18:54:25.424306","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:13:58.539735Z","iopub.execute_input":"2022-12-05T20:13:58.541045Z","iopub.status.idle":"2022-12-05T20:14:10.222641Z","shell.execute_reply.started":"2022-12-05T20:13:58.540962Z","shell.execute_reply":"2022-12-05T20:14:10.220212Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create Baseline Submission\n\nSeeing above that the optimal threshold is 1 yard. We can now compute the distances on the test set and submit with the threshold of 1 yard.\n\n*Note: This is a code competition. When you submit, your model will be rerun on a set of 61 unseen plays located in a holdout test set. The publicly provided test videos are simply a set of mock plays (copied from the training set) which are not used in scoring.*","metadata":{"execution":{"iopub.execute_input":"2022-12-03T00:25:27.442763Z","iopub.status.busy":"2022-12-03T00:25:27.442297Z","iopub.status.idle":"2022-12-03T00:25:38.521793Z","shell.execute_reply":"2022-12-03T00:25:38.520110Z","shell.execute_reply.started":"2022-12-03T00:25:27.442722Z"},"papermill":{"duration":0.245304,"end_time":"2022-12-05T18:54:31.624262","exception":false,"start_time":"2022-12-05T18:54:31.378958","status":"completed"},"tags":[]}},{"cell_type":"code","source":"ss = pd.read_csv(f\"{BASE_DIR}/sample_submission.csv\")\n\nTHRES = 1\n\nss = expand_contact_id(ss)\nss_dist = compute_distance(ss, te_tracking, merge_col=\"step\")\n\nprint(ss_dist.shape, ss.shape)\n\nsubmission = ss_dist[[\"contact_id\", \"distance\"]].copy()\nsubmission[\"contact\"] = (submission[\"distance\"] <= THRES).astype(\"int\")\nsubmission = submission.drop('distance', axis=1)\nsubmission[[\"contact_id\", \"contact\"]].to_csv(\"submission.csv\", index=False)\n\nsubmission.head()","metadata":{"papermill":{"duration":0.701817,"end_time":"2022-12-05T18:54:32.571716","exception":false,"start_time":"2022-12-05T18:54:31.869899","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-12-05T20:14:10.226028Z","iopub.execute_input":"2022-12-05T20:14:10.226769Z","iopub.status.idle":"2022-12-05T20:14:11.272681Z","shell.execute_reply.started":"2022-12-05T20:14:10.226683Z","shell.execute_reply":"2022-12-05T20:14:11.270661Z"},"trusted":true},"execution_count":null,"outputs":[]}]}