{"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":"# Data Exploration\nHi,\n\nI've done some basic data exploration to get my bearings, and decided to release it publicly.\n\nIn this notebook, there are:\n\n1. Basic checks of the columns, to understand what they are\n2. Basic analysis of the annotations - plots of how detection frequency changes in each sequence\n3. A visualiser for the data (you'll have to run the code yourself, as widgets don't work in the preview)\n\nFeel free to use / copy / edit / do anything you like with the code :)\n","metadata":{}},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np\nimport pandas as pd\nimport cv2\nfrom pathlib import Path\nfrom colorama import Fore, Back, Style\nimport matplotlib.pyplot as plt\nimport ipywidgets as widgets\nfrom IPython.display import display, Image\n\n\nROOT_DIR = Path(\"/kaggle/input/tensorflow-great-barrier-reef\")\n\nTRAIN_CSV = ROOT_DIR / \"train.csv\"\nTRAIN_DF = pd.read_csv(TRAIN_CSV)\n\nTEST_CSV = ROOT_DIR / \"test.csv\"\nTEST_DF = pd.read_csv(TEST_CSV)\n\nlist(ROOT_DIR.iterdir())\n\n\n\n# # Input data files are available in the read-only \"../input/\" directory\n# # For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n# import os\n# for \n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in sorted(filenames):\n#         print(os.path.join(dirname, filename))\n\n# # You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# # You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-12-01T10:26:46.552292Z","iopub.execute_input":"2021-12-01T10:26:46.552784Z","iopub.status.idle":"2021-12-01T10:26:46.942555Z","shell.execute_reply.started":"2021-12-01T10:26:46.552686Z","shell.execute_reply":"2021-12-01T10:26:46.941954Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Augment with commonly used things","metadata":{}},{"cell_type":"code","source":"import json\n\n\n# Count detections\nif \"detection_count\" not in TRAIN_DF.columns:\n    det_counts = TRAIN_DF.apply(lambda row: len(eval(row.annotations)), axis=1)\n    TRAIN_DF[\"detection_count\"] = det_counts","metadata":{"execution":{"iopub.status.busy":"2021-12-01T10:26:46.944327Z","iopub.execute_input":"2021-12-01T10:26:46.944566Z","iopub.status.idle":"2021-12-01T10:26:47.709707Z","shell.execute_reply.started":"2021-12-01T10:26:46.944537Z","shell.execute_reply":"2021-12-01T10:26:47.708893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## What are the columns?","metadata":{}},{"cell_type":"code","source":"print(TRAIN_DF.info())\nprint()","metadata":{"execution":{"iopub.status.busy":"2021-12-01T11:05:29.776236Z","iopub.execute_input":"2021-12-01T11:05:29.776550Z","iopub.status.idle":"2021-12-01T11:05:29.796458Z","shell.execute_reply.started":"2021-12-01T11:05:29.776518Z","shell.execute_reply":"2021-12-01T11:05:29.795415Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Is the sequence_frame the same as video_frame","metadata":{}},{"cell_type":"code","source":"eq_frames = TRAIN_DF[TRAIN_DF[\"video_frame\"] == TRAIN_DF[\"sequence_frame\"]]\nuneq_frames = TRAIN_DF[TRAIN_DF[\"video_frame\"] != TRAIN_DF[\"sequence_frame\"]]\nprint(\"eq frames, uneq frames:\", eq_frames.size, uneq_frames.size)\nprint()\n# No. Sequences start from 0, and they probably chopped the video where no starfish are seen\n\n# So, videos are chopped into sequences, and each sequence goes from frame 0 to n","metadata":{"execution":{"iopub.status.busy":"2021-12-01T11:04:18.790348Z","iopub.execute_input":"2021-12-01T11:04:18.790848Z","iopub.status.idle":"2021-12-01T11:04:18.802128Z","shell.execute_reply.started":"2021-12-01T11:04:18.790808Z","shell.execute_reply":"2021-12-01T11:04:18.801311Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Do sequence_frames go from 0 to N nicely?","metadata":{}},{"cell_type":"code","source":"print(\"Sequence frames sequential and start from 0?\")\nfor seq_name in TRAIN_DF[\"sequence\"].unique():\n    sequential = True\n    numbers = TRAIN_DF[TRAIN_DF[\"sequence\"] == seq_name][\"sequence_frame\"].values\n    numbers.sort()\n    \n    i = 0\n    for num in numbers:\n        while i < num:\n            print(f\"Seq {seq_name}: {Fore.RED}Missing {i}{Fore.RESET}\")\n            i += 1\n        i += 1\n\n    if sequential:\n        print(f\"Seq {seq_name}: {Fore.GREEN}Yes{Fore.RESET}\")\nprint()\n# Nice.  ","metadata":{"execution":{"iopub.status.busy":"2021-12-01T11:04:56.298720Z","iopub.execute_input":"2021-12-01T11:04:56.298990Z","iopub.status.idle":"2021-12-01T11:04:56.348774Z","shell.execute_reply.started":"2021-12-01T11:04:56.298961Z","shell.execute_reply":"2021-12-01T11:04:56.347787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Are video_frames sequential?","metadata":{}},{"cell_type":"code","source":"print(\"Video frames sequential?\")\nfor seq_name in TRAIN_DF[\"sequence\"].unique():\n    sequential = True\n    numbers = TRAIN_DF[TRAIN_DF[\"sequence\"] == seq_name][\"video_frame\"].values\n    numbers.sort()\n    \n    i = numbers[0]\n    for num in numbers:\n        while i < num:\n            print(f\"Seq {seq_name}: {Fore.RED}Missing {i}{Fore.RESET}\")\n            i += 1\n        i += 1\n\n    if sequential:\n        print(f\"Seq {seq_name}: {Fore.GREEN}Yes{Fore.RESET}\")\nprint()\n# Cool. Though I wonder if they'd give sequential data during evaluation.\n\n# Are image id's just connected frame id's and video id's?\nnew_vid_ids = TRAIN_DF[\"video_id\"].astype(str) + \"-\" + TRAIN_DF[\"video_frame\"].astype(str)\nprint(\"How many images have strange image_ids:\", (TRAIN_DF[\"image_id\"] != new_vid_ids).sum())\n# Great!","metadata":{"execution":{"iopub.status.busy":"2021-12-01T11:05:19.912861Z","iopub.execute_input":"2021-12-01T11:05:19.913144Z","iopub.status.idle":"2021-12-01T11:05:20.049592Z","shell.execute_reply.started":"2021-12-01T11:05:19.913116Z","shell.execute_reply":"2021-12-01T11:05:20.048542Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Do videos have unique sequence names?","metadata":{}},{"cell_type":"code","source":"vid_seq_pairs = TRAIN_DF[[\"video_id\", \"sequence\"]].drop_duplicates()\nrepeated_sequence_count = (vid_seq_pairs[\"sequence\"].value_counts() != 1).sum()\nprint(\"How many repeated sequences:\", repeated_sequence_count)\n# eq_frames = TRAIN_DF[TRAIN_DF[\"video_frame\"] == TRAIN_DF[\"sequence_frame\"]]\n# uneq_frames = TRAIN_DF[TRAIN_DF[\"video_frame\"] != TRAIN_DF[\"sequence_frame\"]]\n# print(\"eq frames, uneq frames:\", eq_frames.size, uneq_frames.size)\n# print()\n\n# Hmm, but maybe sequences are actually the sequence_ids of the first vid? Like, maybe the total ID is (sequence + sequence_frame)?\n# No matter - sequences are unique and that's enough for analysis.","metadata":{"execution":{"iopub.status.busy":"2021-12-01T11:06:02.870291Z","iopub.execute_input":"2021-12-01T11:06:02.871054Z","iopub.status.idle":"2021-12-01T11:06:02.884646Z","shell.execute_reply.started":"2021-12-01T11:06:02.871008Z","shell.execute_reply":"2021-12-01T11:06:02.883699Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## How are the annotations distributed?","metadata":{}},{"cell_type":"code","source":"print(f\"Starfish per image:\", TRAIN_DF[\"detection_count\"].value_counts())\n\nbin_count = len(TRAIN_DF[\"detection_count\"].unique())\nplot = TRAIN_DF.hist(column=\"detection_count\", figsize=(16,6), bins=bin_count)\nax = plot[0][0]\nax.set_title(\"Starfish count, per image\")\n\nTRAIN_DF_WITH_STARFISH = TRAIN_DF[TRAIN_DF[\"detection_count\"] > 0]\nbin_count = len(TRAIN_DF_WITH_STARFISH[\"detection_count\"].unique())\nplot = TRAIN_DF_WITH_STARFISH.hist(column=\"detection_count\", figsize=(16,4), bins=bin_count)\nax = plot[0][0]\nax.set_title(\"Starfish count, per image (with 0 detections removed)\");","metadata":{"execution":{"iopub.status.busy":"2021-12-01T10:40:54.315977Z","iopub.execute_input":"2021-12-01T10:40:54.316270Z","iopub.status.idle":"2021-12-01T10:40:55.038304Z","shell.execute_reply.started":"2021-12-01T10:40:54.316240Z","shell.execute_reply":"2021-12-01T10:40:55.037331Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## How do detections change during the frame?","metadata":{}},{"cell_type":"code","source":"import math \n\n\nSEQUENCE_COUNT = len(TRAIN_DF[\"sequence\"].drop_duplicates())\nFIG_COLS = 3\nFIG_ROWS = math.ceil(SEQUENCE_COUNT / FIG_COLS)\nfig = plt.figure(figsize=(30, 30), constrained_layout=True)\n# fig.tight_layout(pad=10.0)\n# fig.tight_layout()\n\ndet_data = TRAIN_DF[[\"sequence\", \"video_id\", \"sequence_frame\", \"detection_count\"]].drop_duplicates()\nfor i, seq_num in enumerate(det_data[\"sequence\"].unique()):  # we know seq numbers are unique in train data\n    # Get data\n    seq_data = det_data[det_data[\"sequence\"] == seq_num].sort_values(by=\"sequence_frame\")\n    seq_data = seq_data.set_index(seq_data[\"sequence_frame\"]).drop(columns=\"sequence_frame\")\n    video_id = seq_data[\"video_id\"].iloc[0]\n    \n    # Select figure position\n    col = (i % FIG_COLS) + 1\n    row = (i // FIG_COLS) + 1\n    \n    # Plot\n    ax = plt.subplot(FIG_ROWS, FIG_COLS, i+1)\n    ax = seq_data[\"detection_count\"].plot.line(ax=ax)\n    ax.set_title(f\"Video {video_id}, Sequence {seq_num}\", fontsize=22)\n    ax.yaxis.set_major_locator(plt.MaxNLocator(integer=True))\n    ax.set_xlabel('Detections', fontsize=16)\n    ax.set_ylabel('Sequence Frame', fontsize=16)\n    \n","metadata":{"execution":{"iopub.status.busy":"2021-12-01T10:59:46.464309Z","iopub.execute_input":"2021-12-01T10:59:46.464905Z","iopub.status.idle":"2021-12-01T10:59:52.493594Z","shell.execute_reply.started":"2021-12-01T10:59:46.464870Z","shell.execute_reply":"2021-12-01T10:59:52.492404Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualise Annotations\n> Note: the widgets don't work when previewing the notebook. If you want to use this, copy the notebook and then run it.\n\n> Also, so you aren't surprised: it's not ideal. It takes 0.5s-1.5s to switch between images, as the notebook is not interactive and needs to clear each image before displaying a new one. More annoyingly, the outputs window clears everything and then displays it again, so the window pans up and down which is pretty annoying.","metadata":{}},{"cell_type":"code","source":"#  Cache / Selected values\nvideo_ids = TRAIN_DF[\"video_id\"].unique()\nsel_video_id = 0\nsel_video_df = TRAIN_DF[TRAIN_DF[\"video_id\"] == sel_video_id]\n\nsequences = sel_video_df[\"sequence\"].unique()\nsel_sequence = sequences[0]\nsel_sequence_df = sel_video_df[sel_video_df[\"sequence\"] == sel_sequence]\n\nlast_frame = sel_sequence_df[\"sequence_frame\"].max()\nsel_sequence_frame = 0\nsel_sequence_frames = sel_sequence_df[sel_sequence_df[\"sequence_frame\"] == sel_sequence_frame]\nassert len(sel_sequence_frames) == 1\nsel_video_frame = sel_sequence_frames[\"video_frame\"].values[0]\nsel_annotation = eval(sel_sequence_frames[\"annotations\"].values[0])\n  \n# UI elements\ndd_video_id = widgets.Dropdown(options=video_ids, description='Video ID:')\ndd_sequence = widgets.Dropdown(options=sequences, description='Sequence:')\nbtn_first = widgets.Button(description=\"⏮️\")\nbtn_back_50 = widgets.Button(description=\"⏪\")\nbtn_back = widgets.Button(description=\"◀️\")\nbtn_forward = widgets.Button(description=\"▶️\")\nbtn_forward_50 = widgets.Button(description=\"⏩\")\nbtn_last = widgets.Button(description=\"⏯\")\n\nout = widgets.Output()\n\ndd_row = widgets.HBox([dd_video_id, dd_sequence])\nbtn_row = widgets.HBox([btn_first, btn_back_50, btn_back, btn_forward, btn_forward_50, btn_last])\nall_widgets = widgets.VBox([dd_row, btn_row, out])\n\n# Selection helpers (only change the data)\ndef set_frame(new_number):\n    global sel_sequence_frame\n    global sel_sequence_frames\n    global sel_video_frame\n    global sel_annotation\n    \n    sel_sequence_frame = max(0, min(new_number, last_frame))\n    sel_sequence_frames = sel_sequence_df[sel_sequence_df[\"sequence_frame\"] == sel_sequence_frame]\n    assert len(sel_sequence_frames) == 1\n    sel_video_frame = sel_sequence_frames[\"video_frame\"].values[0]\n    \n    sel_annotation = eval(sel_sequence_frames[\"annotations\"].values[0])\n\ndef set_sequence(new_number):\n    global sel_sequence\n    global sel_sequence_df\n    global last_frame\n\n    sel_sequence = new_number\n    sel_sequence_df = sel_video_df[sel_video_df[\"sequence\"] == sel_sequence]\n    last_frame = sel_sequence_df[\"sequence_frame\"].max()\n\n    set_frame(0)\n    \ndef set_video_id(new_id):\n    global sel_video_id\n    global sel_video_df\n    global sequences\n    \n    sel_video_id = new_id\n    sel_video_df = TRAIN_DF[TRAIN_DF[\"video_id\"] == sel_video_id]\n\n    sequences = sel_video_df[\"sequence\"].unique()\n    set_sequence(sequences[0])\n    \n# UI helpers (handle UI events + call selection helpers)\ndef clear_output():\n    out.clear_output()\n    \ndef draw_image():\n    img_path = ROOT_DIR / \"train_images\" / f\"video_{sel_video_id}\" / f\"{sel_video_frame}.jpg\"\n    assert img_path.is_file(), f\"Cannot find image {img_path}\"\n    cv_img = cv2.imread(str(img_path))\n    cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)\n    \n    draw_bboxes(cv_img, sel_annotation)\n\n    with out:\n        print(\"Image\", img_path)\n\n        # TODO: possible speedup - find a way to update images without clearing output\n        img_fig = plt.figure(figsize=(20,16), facecolor=\"#123456ff\", frameon=False)\n        img_ax = img_fig.add_subplot(1, 1, 1)\n        img_ax.get_xaxis().set_visible(False)\n        img_ax.get_yaxis().set_visible(False)\n        img_ax.use_sticky_edges = False\n        # img_ax.margins(x=0)  # doesn't work. make img_fig.facecolor transparent instead as a hack.\n\n        img_ax.imshow(cv_img)\n        plt.show()\n\n    \ndef draw_bboxes(cv_img, annotations):\n    BBOX_COLOR_RGB = (255,126,0)\n    for ann in annotations:\n        cv2.rectangle(\n            cv_img,\n            (ann[\"x\"] ,ann[\"y\"]),\n            (ann[\"x\"] + ann[\"width\"], ann[\"y\"] + ann[\"height\"]),\n            color=BBOX_COLOR_RGB,\n            thickness=3\n        )\n\ndef on_click_forward(b):\n    with out:\n        set_frame(sel_sequence_frame + 1)\n        clear_output()\n        draw_image()\nbtn_forward.on_click(on_click_forward)\ndef on_click_back(b):\n    with out:\n        set_frame(sel_sequence_frame - 1)\n        clear_output()\n        draw_image()\nbtn_back.on_click(on_click_back)\ndef on_click_forward_50(b):\n    with out:\n        set_frame(sel_sequence_frame + 50)\n        clear_output()\n        draw_image()\nbtn_forward_50.on_click(on_click_forward_50)\ndef on_click_back_50(b):\n    with out:\n        set_frame(sel_sequence_frame - 50)\n        clear_output()\n        draw_image()\nbtn_back_50.on_click(on_click_back_50)\ndef on_click_first(b):\n    with out:\n        set_frame(0)\n        clear_output()\n        draw_image()\nbtn_first.on_click(on_click_first)\ndef on_click_last(b):\n    with out:\n        set_frame(last_frame)\n        clear_output()\n        draw_image()\nbtn_last.on_click(on_click_last)\n\ndef on_sequence_change(change):\n    with out:\n        if change[\"old\"] == change[\"new\"]:\n            return\n        set_sequence(change[\"new\"])\n\n        clear_output()\n        draw_image()\ndd_sequence.observe(on_sequence_change, names=\"value\")\n\ndef on_video_id_change(change):\n    with out:\n        if change[\"old\"] == change[\"new\"]:\n            return\n        set_video_id(change[\"new\"])\n        dd_sequence.options = sequences\n        \n        clear_output()\n        draw_image()\ndd_video_id.observe(on_video_id_change, names=\"value\")\n\n\ndisplay(all_widgets)\ndraw_image()","metadata":{"execution":{"iopub.status.busy":"2021-12-01T10:58:49.752959Z","iopub.execute_input":"2021-12-01T10:58:49.753316Z","iopub.status.idle":"2021-12-01T10:58:50.634973Z","shell.execute_reply.started":"2021-12-01T10:58:49.753280Z","shell.execute_reply":"2021-12-01T10:58:50.634260Z"},"trusted":true},"execution_count":null,"outputs":[]}]}