{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":46105,"databundleVersionId":5087314,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from typing import Dict\nfrom pathlib import Path\nfrom types import SimpleNamespace\nfrom multiprocessing import Pool\nfrom functools import partial\n\nimport polars as pl\nimport matplotlib.pyplot as plt\nimport matplotlib.animation as animation\nfrom IPython.core.display import Video\nfrom tqdm import tqdm\n\nplt.style.use(\"ggplot\")\n\ncfg = SimpleNamespace()\ncfg.INPUT = Path(\"/kaggle/input/asl-signs\")\ncfg.OUTPUT = Path(\"/kaggle/working/animation\")\ncfg.DEBUG = True\n\n%load_ext autoreload\n%autoreload 2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:57.557263Z","iopub.execute_input":"2025-09-20T03:33:57.557581Z","iopub.status.idle":"2025-09-20T03:33:58.382551Z","shell.execute_reply.started":"2025-09-20T03:33:57.557553Z","shell.execute_reply":"2025-09-20T03:33:58.381500Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = pl.read_csv(cfg.INPUT / \"train.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:58.385018Z","iopub.execute_input":"2025-09-20T03:33:58.385369Z","iopub.status.idle":"2025-09-20T03:33:58.546254Z","shell.execute_reply.started":"2025-09-20T03:33:58.385344Z","shell.execute_reply":"2025-09-20T03:33:58.545301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:58.547213Z","iopub.execute_input":"2025-09-20T03:33:58.547495Z","iopub.status.idle":"2025-09-20T03:33:58.593709Z","shell.execute_reply.started":"2025-09-20T03:33:58.547471Z","shell.execute_reply":"2025-09-20T03:33:58.592708Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"# Unique participants: {len(train['participant_id'].unique())}\")\nprint(f\"# Unique sequence: {len(train['sequence_id'].unique()):,}\")\nprint(f\"# Unique signs: {len(train['sign'].unique())}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:58.595163Z","iopub.execute_input":"2025-09-20T03:33:58.595485Z","iopub.status.idle":"2025-09-20T03:33:58.699054Z","shell.execute_reply.started":"2025-09-20T03:33:58.595460Z","shell.execute_reply":"2025-09-20T03:33:58.697905Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sequence_per_participant = (\n    train.group_by(\"participant_id\")\n    .agg(\n        sequence_count=pl.col(\"sequence_id\").unique().count()\n    )\n)\n\nsequence_per_participant","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:58.700264Z","iopub.execute_input":"2025-09-20T03:33:58.700717Z","iopub.status.idle":"2025-09-20T03:33:58.768520Z","shell.execute_reply.started":"2025-09-20T03:33:58.700682Z","shell.execute_reply":"2025-09-20T03:33:58.766831Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sequence_per_participant = (\n    train.group_by(\"participant_id\")\n    .agg(\n        sequence_count=pl.col(\"sequence_id\").unique().count()\n    )\n    .with_columns(pl.col(\"participant_id\").cast(str))\n)\n\nprint(sequence_per_participant)\n\n_, ax, = plt.subplots()\nax.barh(\n    y=sequence_per_participant['participant_id'],\n    width=sequence_per_participant['sequence_count']\n)\nax.set(\n    xlabel=\"#sequences\",\n    ylabel=\"participant_id\",\n    title=\"#sequence per participant\"\n)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:58.769799Z","iopub.execute_input":"2025-09-20T03:33:58.770211Z","iopub.status.idle":"2025-09-20T03:33:59.270011Z","shell.execute_reply.started":"2025-09-20T03:33:58.770177Z","shell.execute_reply":"2025-09-20T03:33:59.268881Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sequence_per_sign = (\n    train.group_by(\"sign\").agg(\n        sequence_count=pl.col(\"sequence_id\").count()\n    ).sort(\"sequence_count\")\n)\n\nprint(sequence_per_sign)\n\n_, ax = plt.subplots()\nax.hist(sequence_per_sign[\"sequence_count\"], bins=20)\nax.set(xlabel=\"#sequences/sign\", ylabel=\"count\", title=\"#sequences per sign\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:59.273466Z","iopub.execute_input":"2025-09-20T03:33:59.273877Z","iopub.status.idle":"2025-09-20T03:33:59.554944Z","shell.execute_reply.started":"2025-09-20T03:33:59.273851Z","shell.execute_reply":"2025-09-20T03:33:59.553792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sequence_per_sign = (\n    train.group_by(\"sign\").agg(\n        pl.col(\"participant_id\").count()\n    ).sort(\"participant_id\").select(sign=pl.col(\"sign\") ,participant_count=pl.col(\"participant_id\"))\n)\n\nprint(sequence_per_sign)\n\n_, ax = plt.subplots()\nax.hist(sequence_per_sign[\"participant_count\"], bins=20)\nax.set(xlabel=\"#participants/sign\", ylabel=\"count\", title=\"#participants per sign\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:59.556151Z","iopub.execute_input":"2025-09-20T03:33:59.556554Z","iopub.status.idle":"2025-09-20T03:33:59.828887Z","shell.execute_reply.started":"2025-09-20T03:33:59.556516Z","shell.execute_reply":"2025-09-20T03:33:59.827801Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"edges = {\n    \"left_hand\": [\n        (0, 1),\n        (1, 2),\n        (2, 3),\n        (3, 4),\n        (0, 5),\n        (0, 17),\n        (5, 6),\n        (6, 7),\n        (7, 8),\n        (5, 9),\n        (9, 10),\n        (10, 11),\n        (11, 12),\n        (9, 13),\n        (13, 14),\n        (14, 15),\n        (15, 16),\n        (13, 17),\n        (17, 18),\n        (18, 19),\n        (19, 20),\n    ],\n    \"right_hand\": [\n        (0, 1),\n        (1, 2),\n        (2, 3),\n        (3, 4),\n        (0, 5),\n        (0, 17),\n        (5, 6),\n        (6, 7),\n        (7, 8),\n        (5, 9),\n        (9, 10),\n        (10, 11),\n        (11, 12),\n        (9, 13),\n        (13, 14),\n        (14, 15),\n        (15, 16),\n        (13, 17),\n        (17, 18),\n        (18, 19),\n        (19, 20),\n    ],\n    \"pose\": [\n        (8, 6),\n        (6, 5),\n        (6, 4),\n        (4, 0),\n        (0, 1),\n        (1, 2),\n        (2, 3),\n        (3, 7),\n        (10, 9),\n        (11, 12),\n        (11, 13),\n        (11, 23),\n        (13, 15),\n        (15, 21),\n        (15, 17),\n        (15, 19),\n        (17, 19),\n        (12, 14),\n        (12, 24),\n        (14, 16),\n        (16, 22),\n        (16, 20),\n        (16, 18),\n        (18, 20),\n        (23, 24),\n        # discard landmarks of lower body\n        # (24, 26),\n        # (26, 28),\n        # (28, 30),\n        # (28, 32),\n        # (30, 32),\n        # (23, 25),\n        # (25, 27),\n        # (27, 29),\n        # (27, 31),\n        # (29, 31),\n    ],\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:59.829941Z","iopub.execute_input":"2025-09-20T03:33:59.830287Z","iopub.status.idle":"2025-09-20T03:33:59.854694Z","shell.execute_reply.started":"2025-09-20T03:33:59.830250Z","shell.execute_reply":"2025-09-20T03:33:59.853221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lm_data = {k: v for k, v in zip(train.columns, train.row(10))}\n\ndf_landmark = pl.read_parquet(cfg.INPUT / lm_data[\"path\"])\nlm_first_frame = df_landmark.partition_by(\"frame\")[0]\nlms = lm_first_frame.partition_by(\"type\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:59.856316Z","iopub.execute_input":"2025-09-20T03:33:59.856651Z","iopub.status.idle":"2025-09-20T03:33:59.944786Z","shell.execute_reply.started":"2025-09-20T03:33:59.856624Z","shell.execute_reply":"2025-09-20T03:33:59.943695Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lm_first_frame.partition_by(\"type\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:59.945731Z","iopub.execute_input":"2025-09-20T03:33:59.946093Z","iopub.status.idle":"2025-09-20T03:33:59.972529Z","shell.execute_reply.started":"2025-09-20T03:33:59.946062Z","shell.execute_reply":"2025-09-20T03:33:59.971035Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for lm in lms:\n    lm = lm.filter(\n        (pl.col(\"type\") != \"pose\") | (pl.col(\"landmark_index\") < 25)\n    )    \n    print(lm.row(0)[2])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:33:59.973675Z","iopub.execute_input":"2025-09-20T03:33:59.974009Z","iopub.status.idle":"2025-09-20T03:34:00.014351Z","shell.execute_reply.started":"2025-09-20T03:33:59.973984Z","shell.execute_reply":"2025-09-20T03:34:00.013189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"_, axes = plt.subplots(2,2,figsize=(8,8))\naxes = axes.ravel()\n\nfor lm, ax in zip(lms, axes):\n    lm = lm.filter(\n        (pl.col(\"type\") != \"pose\") | (pl.col(\"landmark_index\") < 25)\n    )\n    lm_type = lm.row(0)[2]\n    ax.scatter(lm[\"x\"], lm[\"y\"])\n\n    if lm_type != \"face\":\n        for row in lm.iter_rows():\n            dt = {k: v for k, v in zip(lm.columns, row)}\n            x, y, idx = dt[\"x\"], dt[\"y\"], dt[\"landmark_index\"]\n\n            if (x is not None) & (y is not None):\n                ax.text(x,y,idx)\n\n        for edge in edges[lm_type]:\n            i, j = edge\n            x1, x2, y1, y2 = lm[\"x\"][i], lm[\"x\"][j], lm[\"y\"][i], lm[\"y\"][j]\n            if not ((x1 is None) | (x2 is None) | (y1 is None) | (y2 is None)):\n                ax.plot((x1, x2), (y1, y2), color=\"gray\")\n\n    ax.set(title=f\"{lm_type}\")\n    ax.invert_yaxis()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:34:00.015690Z","iopub.execute_input":"2025-09-20T03:34:00.016117Z","iopub.status.idle":"2025-09-20T03:34:01.102210Z","shell.execute_reply.started":"2025-09-20T03:34:00.016092Z","shell.execute_reply":"2025-09-20T03:34:01.100834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"_, axes = plt.subplots(1,3,figsize=(12,4))\n\nfor ax, col in zip(axes, [\"x\", \"y\", \"z\"]):\n    ax.hist(df_landmark[col], bins=20, alpha=0.5, label=col)\n    ax.set(xlabel=col)\n\nplt.title(\"Distribution of axes of normalized co-ordinate\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:34:01.103402Z","iopub.execute_input":"2025-09-20T03:34:01.103826Z","iopub.status.idle":"2025-09-20T03:34:01.775371Z","shell.execute_reply.started":"2025-09-20T03:34:01.103793Z","shell.execute_reply":"2025-09-20T03:34:01.774257Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_animation(row, columns, fps: int = 5):\n    data = {k: v for k, v in zip(columns, row)}\n    sign, participant_id, sequence_id = (\n        data[\"sign\"],\n        data[\"participant_id\"],\n        data[\"sequence_id\"],\n    )\n\n    df_landmark = pl.read_parquet(cfg.INPUT / data[\"path\"])\n    use_cols = [\"x\", \"y\", \"z\"]\n    df_landmark = df_landmark.sort([\"frame\", \"type\", \"landmark_index\"]).with_columns(\n        [pl.col(col).interpolate().over([\"type\", \"landmark_index\"]) for col in use_cols]\n    )\n\n    fig, axes = plt.subplots(2, 2, figsize=(8, 8))\n    axes = axes.ravel()\n\n    lms_all = df_landmark.partition_by(\"frame\")\n\n    def draw_frame(frame):\n        lms = lms_all[frame].partition_by(\"type\")\n\n        for lm, ax in zip(lms, axes):\n            lm = lm.filter((pl.col(\"type\") != \"pose\") | (pl.col(\"landmark_index\") < 25))\n            ax.cla()\n            lm_type = lm.row(0)[2]\n            frame = lm.row(0)[0]\n\n            ax.scatter(lm[\"x\"], lm[\"y\"])\n            if lm_type != \"face\":\n                for row in lm.iter_rows():\n                    dt = {k: v for k, v in zip(lm.columns, row)}\n                    if (dt[\"x\"] is not None) & (dt[\"y\"] is not None):\n                        ax.text(dt[\"x\"], dt[\"y\"], dt[\"landmark_index\"])\n            if lm_type in [\"left_hand\", \"right_hand\", \"pose\"]:\n                for edge in edges[lm_type]:\n                    i, j = edge\n                    x1, x2, y1, y2 = lm[\"x\"][i], lm[\"x\"][j], lm[\"y\"][i], lm[\"y\"][j]\n                    if not ((x1 is None) | (x2 is None) | (y1 is None) | (y2 is None)):\n                        ax.plot((x1, x2), (y1, y2), color=\"gray\")\n            ax.set(title=f\"{lm_type}\")\n            ax.invert_yaxis()\n        plt.suptitle(f'sign: \"{sign}\" [frame={frame}]')\n\n    ani = animation.FuncAnimation(\n        fig, draw_frame, frames=range(len(lms_all)), interval=1000 / fps\n    )\n\n    if not (cfg.OUTPUT / sign).exists():\n        (cfg.OUTPUT / sign).mkdir()\n    ani.save(\n        cfg.OUTPUT / sign / f\"{participant_id}_{sequence_id}.mp4\",\n        writer=\"ffmpeg\",\n        fps=fps,\n        codec=\"h264\",\n    )\n    plt.close(fig)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:34:01.776404Z","iopub.execute_input":"2025-09-20T03:34:01.776756Z","iopub.status.idle":"2025-09-20T03:34:01.804707Z","shell.execute_reply.started":"2025-09-20T03:34:01.776726Z","shell.execute_reply":"2025-09-20T03:34:01.803563Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Choose 20 samples per group","metadata":{}},{"cell_type":"code","source":"train_unique_signs = train.filter(\n    (pl.arange(0, pl.len())).shuffle(seed=42).over(\"sign\") < 20\n)\n\ntrain_unique_signs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:34:01.806086Z","iopub.execute_input":"2025-09-20T03:34:01.806450Z","iopub.status.idle":"2025-09-20T03:34:01.851718Z","shell.execute_reply.started":"2025-09-20T03:34:01.806422Z","shell.execute_reply":"2025-09-20T03:34:01.850332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_unique_signs.group_by(\"sign\").agg(pl.len()).head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:34:01.853137Z","iopub.execute_input":"2025-09-20T03:34:01.853597Z","iopub.status.idle":"2025-09-20T03:34:01.877587Z","shell.execute_reply.started":"2025-09-20T03:34:01.853489Z","shell.execute_reply":"2025-09-20T03:34:01.876419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if not cfg.OUTPUT.exists():\n    cfg.OUTPUT.mkdir()\n\nif cfg.DEBUG:\n    df = train_unique_signs.head(4)\nelse:\n    df = train_unique_signs\n\nfor row in tqdm(df.iter_rows(), total=len(df)):\n    make_animation(row, df.columns)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:34:01.878717Z","iopub.execute_input":"2025-09-20T03:34:01.879075Z","iopub.status.idle":"2025-09-20T03:34:46.636709Z","shell.execute_reply.started":"2025-09-20T03:34:01.879043Z","shell.execute_reply":"2025-09-20T03:34:46.635633Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!du -sh animation","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:34:46.637657Z","iopub.execute_input":"2025-09-20T03:34:46.637953Z","iopub.status.idle":"2025-09-20T03:34:46.783725Z","shell.execute_reply.started":"2025-09-20T03:34:46.637923Z","shell.execute_reply":"2025-09-20T03:34:46.782313Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls animation/table","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:38:04.230910Z","iopub.execute_input":"2025-09-20T03:38:04.231268Z","iopub.status.idle":"2025-09-20T03:38:04.378686Z","shell.execute_reply.started":"2025-09-20T03:38:04.231238Z","shell.execute_reply":"2025-09-20T03:38:04.376641Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cp animation/have/49445_1025455481.mp4 sample001.mp4\n!cp animation/jacket/55372_1008254374.mp4 sample002.mp4\n!cp animation/night/61333_1014909824.mp4 sample003.mp4\n!cp animation/table/55372_1044122718.mp4 sample004.mp4","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:38:08.856472Z","iopub.execute_input":"2025-09-20T03:38:08.856860Z","iopub.status.idle":"2025-09-20T03:38:09.373053Z","shell.execute_reply.started":"2025-09-20T03:38:08.856830Z","shell.execute_reply":"2025-09-20T03:38:09.371340Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"Video(\"sample001.mp4\", embed=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:38:12.644124Z","iopub.execute_input":"2025-09-20T03:38:12.644602Z","iopub.status.idle":"2025-09-20T03:38:12.678118Z","shell.execute_reply.started":"2025-09-20T03:38:12.644565Z","shell.execute_reply":"2025-09-20T03:38:12.676622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"Video(\"sample003.mp4\", embed=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T03:38:20.343235Z","iopub.execute_input":"2025-09-20T03:38:20.343601Z","iopub.status.idle":"2025-09-20T03:38:20.374128Z","shell.execute_reply.started":"2025-09-20T03:38:20.343572Z","shell.execute_reply":"2025-09-20T03:38:20.372846Z"}},"outputs":[],"execution_count":null}]}