{"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":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom tqdm.notebook import tqdm\nfrom collections import Counter\nfrom scipy.ndimage.interpolation import rotate","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-10-24T11:48:40.398797Z","iopub.execute_input":"2022-10-24T11:48:40.399213Z","iopub.status.idle":"2022-10-24T11:48:40.712399Z","shell.execute_reply.started":"2022-10-24T11:48:40.399180Z","shell.execute_reply":"2022-10-24T11:48:40.710989Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Rotation\nIn the big-data-derby-2022 competition, information about a racehorse's speed and average curvature is critical. \n\nThis information can be calculated as numerical approximations from the latitude and the longitude.\n\nOn the other hand, since the track is skewed. \n\nThe skewed path implies a rounding error around the x and y axes. So, I think there is some need for rotation.\n\n\n* This document introduces heuristic rotation.","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(\"../input/big-data-derby-2022/nyra_tracking_table.csv\")\ndf = df.sort_values(by=['track_id','race_date','race_number','program_number','trakus_index']).reset_index(drop=True)\ndf.head(10)","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:48:42.268523Z","iopub.execute_input":"2022-10-24T11:48:42.269069Z","iopub.status.idle":"2022-10-24T11:48:55.353515Z","shell.execute_reply.started":"2022-10-24T11:48:42.269022Z","shell.execute_reply":"2022-10-24T11:48:55.352595Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Skew","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(15, 5))\nfor i, track_id in enumerate(['AQU','BEL','SAR']):\n    print(f\"track_id :{track_id}\")\n    position = df.loc[(df.track_id == track_id), ['longitude','latitude']].to_numpy()\n    plt.subplot(1,3,i+1)\n    plt.title(f\"track_id :{track_id}\")\n    plt.scatter(position[::100, 0], position[::100, 1])\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:48:55.355065Z","iopub.execute_input":"2022-10-24T11:48:55.355705Z","iopub.status.idle":"2022-10-24T11:48:57.532884Z","shell.execute_reply.started":"2022-10-24T11:48:55.355645Z","shell.execute_reply":"2022-10-24T11:48:57.531782Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Find a rotate angle","metadata":{}},{"cell_type":"code","source":"def get_feature(image):\n    x_proj = image.sum(axis=0)\n    y_proj = image.sum(axis=1)\n        \n    x_count = (x_proj > x_proj.mean() - x_proj.std()).sum()\n    y_count = (y_proj > y_proj.mean()).sum()\n    \n    if y_count > 0:\n        return x_count/y_count\n    else:\n        return 0.","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:49:04.208497Z","iopub.execute_input":"2022-10-24T11:49:04.208891Z","iopub.status.idle":"2022-10-24T11:49:04.216189Z","shell.execute_reply.started":"2022-10-24T11:49:04.208860Z","shell.execute_reply":"2022-10-24T11:49:04.214907Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"track_id_list = ['AQU','BEL','SAR']\nposition_list = []\nposition_image_list = []\n\nfor track_id in track_id_list:\n    position = df.loc[(df.track_id==track_id), ['longitude','latitude']].to_numpy()\n    \n    # round position\n    rounded_pos = (position*1e5).round().astype('int')\n    x_max, y_max = rounded_pos.max(axis=0)\n    x_min, y_min = rounded_pos.min(axis=0)\n    pos_img = np.zeros([y_max - y_min + 1, x_max-x_min + 1])\n    \n    pos_count_dict = Counter([tuple(x) for x in rounded_pos])\n    for key in pos_count_dict:\n        pos_img[key[1] -y_min,key[0]-x_min] = 1\n\n    position_list.append(position)\n    position_image_list.append(pos_img)","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:49:13.942419Z","iopub.execute_input":"2022-10-24T11:49:13.942852Z","iopub.status.idle":"2022-10-24T11:49:24.765779Z","shell.execute_reply.started":"2022-10-24T11:49:13.942807Z","shell.execute_reply":"2022-10-24T11:49:24.764562Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# plot pos_img\nplt.figure(figsize=(12,4))\nfor i, item in enumerate(zip(track_id_list, position_image_list)):\n    track_id, pos_img = item\n    plt.subplot(1,3,i+1)\n    plt.title(f\"track_id :{track_id}\")\n    plt.imshow(pos_img, origin='lower')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:49:27.814150Z","iopub.execute_input":"2022-10-24T11:49:27.814547Z","iopub.status.idle":"2022-10-24T11:49:28.371652Z","shell.execute_reply.started":"2022-10-24T11:49:27.814516Z","shell.execute_reply":"2022-10-24T11:49:28.370716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"angle_space = np.linspace(0, 180, 1000)\n\nmean_angle_list = []\nlist_of_feature_list = []\nfor pos_img in position_image_list:\n    feature_list = []\n    for angle in tqdm(angle_space):\n        rotated_pos_img = rotate(pos_img, angle)\n        cur_feature = get_feature(rotated_pos_img)\n        feature_list.append(cur_feature)\n        \n    feature_list = np.array(feature_list)\n    \n    max_idx = np.where(feature_list==np.max(feature_list))[0]\n    mean_angle = angle_space[max_idx].mean()\n    \n    mean_angle_list.append(mean_angle)\n    list_of_feature_list.append(feature_list)\nprint(mean_angle_list)","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:49:33.182221Z","iopub.execute_input":"2022-10-24T11:49:33.183429Z","iopub.status.idle":"2022-10-24T11:55:47.178115Z","shell.execute_reply.started":"2022-10-24T11:49:33.183377Z","shell.execute_reply":"2022-10-24T11:55:47.176776Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# plot\nplt.figure(figsize=(18,8))\n\nfor i in range(3):\n    track_id = track_id_list[i]\n    pos_img = position_image_list[i]\n    mean_angle = mean_angle_list[i]\n    feature_list = list_of_feature_list[i]\n    \n    rotated_pos_img = rotate(pos_img, mean_angle)\n    plt.subplot(2,3,i+1)\n    plt.title(f\"track_id :{track_id}\")\n    plt.imshow(rotated_pos_img, origin='lower')\n    plt.subplot(2,3,i+4)\n    plt.title(f\"features with respect to rotation angles\")\n    plt.plot(feature_list)\n    plt.xticks(np.arange(0, len(feature_list), len(feature_list)//10), \n               angle_space[::len(angle_space)//10].round(2), rotation=45)\n\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:55:51.988968Z","iopub.execute_input":"2022-10-24T11:55:51.989566Z","iopub.status.idle":"2022-10-24T11:55:53.571760Z","shell.execute_reply.started":"2022-10-24T11:55:51.989533Z","shell.execute_reply":"2022-10-24T11:55:53.570330Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Rotate the array","metadata":{}},{"cell_type":"code","source":"def rotate_arr_clockwise(arr, angle):\n    rad_angle = np.pi/180*angle\n    mat = np.array([[np.cos(rad_angle), np.sin(rad_angle)],\n                    [- np.sin(rad_angle), np.cos(rad_angle)]])\n    return np.dot(mat, arr.T).T","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:56:48.932967Z","iopub.execute_input":"2022-10-24T11:56:48.933908Z","iopub.status.idle":"2022-10-24T11:56:48.941125Z","shell.execute_reply.started":"2022-10-24T11:56:48.933797Z","shell.execute_reply":"2022-10-24T11:56:48.939715Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"angle_items = dict(zip(track_id_list, mean_angle_list))\nangle_items","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:56:54.891455Z","iopub.execute_input":"2022-10-24T11:56:54.891873Z","iopub.status.idle":"2022-10-24T11:56:54.900021Z","shell.execute_reply.started":"2022-10-24T11:56:54.891841Z","shell.execute_reply":"2022-10-24T11:56:54.898798Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(18, 4))\nfor idx, (track_id, mean_angle) in enumerate(angle_items.items()):\n    position = position_list[idx]\n    rotated_position = rotate_arr_clockwise(position, mean_angle)\n    \n    # if start position x of some player is larger than center pos \n    if rotated_position[0, 0] > rotated_position[:, 0].mean():\n        mean_angle += 180\n        angle_items[track_id] = mean_angle\n        rotated_position = rotate_arr_clockwise(position, mean_angle)\n        \n    plt.subplot(1, 3, idx+1)\n    plt.title(f\"track_id: {track_id}\")\n    plt.scatter(rotated_position[:, 0], rotated_position[:, 1], s=1)\n    plt.grid(color='lightgray', linestyle='--', linewidth=1, alpha=0.5)\n\nplt.show()\nprint(f\"final rotate angle: {angle_items}\")","metadata":{"execution":{"iopub.status.busy":"2022-10-24T11:56:55.634706Z","iopub.execute_input":"2022-10-24T11:56:55.635143Z","iopub.status.idle":"2022-10-24T11:56:58.746569Z","shell.execute_reply.started":"2022-10-24T11:56:55.635105Z","shell.execute_reply":"2022-10-24T11:56:58.745750Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This is a heuristic method based on the idea that minimize vertical size, and maximize horizontal size.\n\nThanks.","metadata":{}}]}