{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.12","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":30396,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Sign Language EDA\n\nIn this competition, we are classifying American Sign Language (ASL) signs using hand landmarks. Although from first reading, sign language recognition will immediately give an idea of a computer vision contest, it is not. We will be dealing solely with tabular data.\n\n**What are we predicting:** Classifying signs based on coordinates signals captured from videos.\n\n**What are we submitting:** Tensorflow Lite model which will take as input one dataframe per video sequence and output its sign label.","metadata":{}},{"cell_type":"markdown","source":"**Please leave an upvote if this was helpful!**","metadata":{}},{"cell_type":"markdown","source":"![Basics](https://www.dummies.com/wp-content/uploads/321607.image0.jpg)","metadata":{}},{"cell_type":"markdown","source":"# Update so far: 4th March\n(Might make more sense after reading the later part)\n\n1. You don't essentially need tensorflow knowledge to participate. You can train your models in pytorch, convert it to tensorflow, then tf-lite and submit. I made a notebook [here](https://www.kaggle.com/code/mayukh18/end-to-end-pytorch-training-submission) and an associated [discussion](https://www.kaggle.com/competitions/asl-signs/discussion/391301). There are multiple other examples too.\n\n2. Hands contain the bulk of the useful data. There are 468 face landmarks and 33 pose landmarks but they do not undergo much movement during the sequence. Except for lips, which might be [important](https://www.kaggle.com/competitions/asl-signs/discussion/391812). Credit to Andrew.\n\n3. All the signs are supposed to be done by one hand. So in most sequences, landmarks of one hand are NaNs. How we handle NaNs is an important part of this competition. Failure to handle NaNs in the final tensorflow model can get your score close to zero.\n\n4. It seems pretty decent models can be done with just aggregation data over frames like mean and std which is pretty astonishing I would say.\n\n5. Last but not the least, there is a huge importance on how we design our cross-validation. There is a good gap between CV and LB scores. The best idea given by Carno Zhao [here](https://www.kaggle.com/competitions/asl-signs/discussion/391203#2163584) is to do a stratified grouped KFold cross-validation. The reason being there is a very high chance that the test set has completely new participants not in the train set. Hence it makes sense if our cross-validation is designed in such a way that participants do not overlap between the validation and train segments in each fold.","metadata":{}},{"cell_type":"code","source":"import os\nimport glob\nimport tqdm\nimport random\n\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nLANDMARK_FILES_DIR = \"/kaggle/input/asl-signs/train_landmark_files\"\nTRAIN_FILE = \"/kaggle/input/asl-signs/train.csv\"","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-04-28T04:24:04.114509Z","iopub.execute_input":"2025-04-28T04:24:04.115434Z","iopub.status.idle":"2025-04-28T04:24:04.142565Z","shell.execute_reply.started":"2025-04-28T04:24:04.115325Z","shell.execute_reply":"2025-04-28T04:24:04.141423Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Overall Nature of Data\n\nWe have x-y-z coordinates of landmark indices of hand,face and pose for each frame of a sequence. We need to use all/some of the frames to classify the sequence as a whole into the 250 odd signs there are. There are a total of 21 participants in training data and close to ~4500 sequences per person. Below are the landmark indices of hand from the mediapipe page. There's no public test data in this competition.","metadata":{}},{"cell_type":"code","source":"participants = os.listdir(LANDMARK_FILES_DIR)\nprint(f\"Total number of participants = {len(participants)}\")\nprint(f\"Average number of sequences per participant = {len(glob.glob(LANDMARK_FILES_DIR + '/*/*.parquet'))/len(participants)}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-04-28T04:24:19.817539Z","iopub.execute_input":"2025-04-28T04:24:19.818267Z","iopub.status.idle":"2025-04-28T04:24:24.713235Z","shell.execute_reply.started":"2025-04-28T04:24:19.818230Z","shell.execute_reply":"2025-04-28T04:24:24.712117Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"![Hand Landmarks [1]](https://mediapipe.dev/images/mobile/hand_landmarks.png)","metadata":{}},{"cell_type":"markdown","source":"# Sequence Landmarks Data\n\nLets have a look at the dataframe of one sample sequence!","metadata":{}},{"cell_type":"code","source":"sample = pd.read_parquet(\"/kaggle/input/asl-signs/train_landmark_files/16069/100015657.parquet\")\nprint(f\"Sample shape = {sample.shape}\")\nsample.sample(10)","metadata":{"_kg_hide-input":false,"execution":{"iopub.status.busy":"2025-04-28T04:24:36.544798Z","iopub.execute_input":"2025-04-28T04:24:36.545203Z","iopub.status.idle":"2025-04-28T04:24:36.747618Z","shell.execute_reply.started":"2025-04-28T04:24:36.545170Z","shell.execute_reply":"2025-04-28T04:24:36.746578Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"As we can see, there are x-y-z locations for all the different body parts. We can perhaps combine all of the details of a frame in a single row when we move on to the modeling part. Here it gives us a little more detailed look into the data. (207-103)=104 frames in this sequence.","metadata":{}},{"cell_type":"code","source":"sample.describe()","metadata":{"_kg_hide-input":false,"execution":{"iopub.status.busy":"2025-04-28T04:24:49.521171Z","iopub.execute_input":"2025-04-28T04:24:49.522244Z","iopub.status.idle":"2025-04-28T04:24:49.564686Z","shell.execute_reply.started":"2025-04-28T04:24:49.522203Z","shell.execute_reply":"2025-04-28T04:24:49.563438Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"All different types of landmark = {sample.type.unique()}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-04-28T04:24:58.250082Z","iopub.execute_input":"2025-04-28T04:24:58.250507Z","iopub.status.idle":"2025-04-28T04:24:58.259055Z","shell.execute_reply.started":"2025-04-28T04:24:58.250472Z","shell.execute_reply":"2025-04-28T04:24:58.257820Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We see a few negative coordinates above. Verbatim from the mediapipe page:\n> MULTI_HAND_LANDMARKS\nCollection of detected/tracked hands, where each hand is represented as a list of 21 hand landmarks and each landmark is composed of x, y and z. x and y are normalized to [0.0, 1.0] by the image width and height respectively. z represents the landmark depth with the depth at the wrist being the origin, and the smaller the value the closer the landmark is to the camera. The magnitude of z uses roughly the same scale as x.\n\nSo negative values are not expected for x and y perhaps.\nIt is also important to note that for a good chunk of the video either or both hands will not be visible or in other words, will not have any landmark data. Check the nulls in the data below:","metadata":{}},{"cell_type":"code","source":"sample_left_hand = sample[sample.type == \"left_hand\"]\nsample_right_hand = sample[sample.type == \"right_hand\"]\n\nprint(f\"Percentage of nulls in Left Hand data = {100*np.mean(sample_left_hand['x'].isnull()):.02f} %\")\nprint(f\"Percentage of nulls in Right Hand data = {100*np.mean(sample_right_hand['x'].isnull()):.02f} %\")","metadata":{"execution":{"iopub.status.busy":"2025-04-28T04:25:04.289430Z","iopub.execute_input":"2025-04-28T04:25:04.290624Z","iopub.status.idle":"2025-04-28T04:25:04.312037Z","shell.execute_reply.started":"2025-04-28T04:25:04.290549Z","shell.execute_reply":"2025-04-28T04:25:04.310634Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Visualizing\nVisualizing the hand sequences will enable us to understand the tabular data in a better way. You can see there are lots of rotations and movements going on in these frames. With the coordinates all jumbled up, depth (z axis) surely comes into play although organizers provided some caution regarding it.","metadata":{}},{"cell_type":"code","source":"edges = [(0,1),(1,2),(2,3),(3,4),(0,5),(0,17),(5,6),(6,7),(7,8),(5,9),(9,10),(10,11),(11,12),\n         (9,13),(13,14),(14,15),(15,16),(13,17),(17,18),(18,19),(19,20)]\n\ndef plot_frame(df, frame_id, ax):\n    df = df[df.frame == frame_id].sort_values(['landmark_index'])\n    x = list(df.x)\n    y = list(df.y)\n    \n    ax.scatter(df.x, df.y, color='dodgerblue')\n    for i in range(len(x)):\n        ax.text(x[i], y[i], str(i))\n        \n    for edge in edges:\n        ax.plot([x[edge[0]], x[edge[1]]], [y[edge[0]], y[edge[1]]], color='salmon')\n        ax.set_xlabel(f\"Frame no. {frame_id}\")\n        ax.set_xticks([])\n        ax.set_yticks([])\n        ax.set_xticklabels([])\n        ax.set_yticklabels([])\n\n    \ndef plot_frame_seq(df, frame_range, n_frames):\n    frames = np.linspace(frame_range[0],frame_range[1],n_frames, dtype = int, endpoint=True)\n    fig, ax = plt.subplots(n_frames, 1, figsize=(5,25))\n    for i in range(n_frames):\n        plot_frame(df, frames[i], ax[i])\n        \n    plt.show()\n\n    \nplot_frame_seq(sample_left_hand, (178,186), 5)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-04-28T04:25:16.528361Z","iopub.execute_input":"2025-04-28T04:25:16.528787Z","iopub.status.idle":"2025-04-28T04:25:17.264817Z","shell.execute_reply.started":"2025-04-28T04:25:16.528743Z","shell.execute_reply":"2025-04-28T04:25:17.263608Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train Data\n\nNow moving on to the actual train file. It has the labels for all the sequences of the participants.","metadata":{}},{"cell_type":"code","source":"train = pd.read_csv(TRAIN_FILE)\nprint(f\"Train shape = {train.shape}\")\ntrain.sample(10)","metadata":{"execution":{"iopub.status.busy":"2025-04-28T04:25:54.312514Z","iopub.execute_input":"2025-04-28T04:25:54.313624Z","iopub.status.idle":"2025-04-28T04:25:54.535797Z","shell.execute_reply.started":"2025-04-28T04:25:54.313543Z","shell.execute_reply":"2025-04-28T04:25:54.534645Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Our classification labels are **sign** whereas the other columns are pretty self explanatory. We have a very balanced distribution of signs per participant, with the vast majority of the participants covering all the 250 signs as can be seen below. Same kind of goes for total number of videos per signs, with the minimum and maximum number of videos per sign being 299 and 415 respectively.","metadata":{}},{"cell_type":"code","source":"train.groupby(['participant_id']).agg(unique_signs=('sign', 'nunique')).reset_index()","metadata":{"execution":{"iopub.status.busy":"2025-04-28T04:26:04.441327Z","iopub.execute_input":"2025-04-28T04:26:04.442100Z","iopub.status.idle":"2025-04-28T04:26:04.491833Z","shell.execute_reply.started":"2025-04-28T04:26:04.442062Z","shell.execute_reply":"2025-04-28T04:26:04.490317Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"count_per_sign = train.groupby(['sign'])['sequence_id'].count().reset_index().sort_values(['sequence_id'], ascending=False)\nfig, ax = plt.subplots(1, 2, figsize=(16,5))\nax[0].bar(range(10), count_per_sign['sequence_id'][:10], color='salmon')\nax[1].bar(range(10), count_per_sign['sequence_id'][-10:], color='dodgerblue')\n\nax[0].set_xticks(range(10))\nax[1].set_xticks(range(10))\n\nax[0].set_xticklabels(count_per_sign['sign'][:10])\nax[1].set_xticklabels(count_per_sign['sign'][-10:])\n\nax[0].set_ylim(250,420)\nax[1].set_ylim(250,420)\n\nax[0].set_xlabel(\"Most frequent signs\")\nax[1].set_xlabel(\"Least frequent signs\")\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-04-28T04:26:09.841215Z","iopub.execute_input":"2025-04-28T04:26:09.842497Z","iopub.status.idle":"2025-04-28T04:26:10.287262Z","shell.execute_reply.started":"2025-04-28T04:26:09.842442Z","shell.execute_reply":"2025-04-28T04:26:10.286083Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Below are all the 250 signs in the training data:","metadata":{}},{"cell_type":"code","source":"word_dict = [[] for i in range(26)]\nfor word in train['sign'].unique():\n    word_dict[ord(word[0].lower()) - 97].append(word)\nfor i in range(26):\n    print(chr(i+97), str(sorted(word_dict[i])))","metadata":{"execution":{"iopub.status.busy":"2025-04-28T04:26:14.832960Z","iopub.execute_input":"2025-04-28T04:26:14.833363Z","iopub.status.idle":"2025-04-28T04:26:14.847751Z","shell.execute_reply.started":"2025-04-28T04:26:14.833330Z","shell.execute_reply":"2025-04-28T04:26:14.846618Z"},"_kg_hide-input":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Details of Sign Sequence Data\nCombining the train and sequence landmarks data,we take a look at how different hands are used in a sign or how elaborate (long) they are. It seems like in general left hand is used slightly more than the right hand and use of both hands simultaneously is very rare. ","metadata":{}},{"cell_type":"code","source":"def get_details_per_sign(sign):\n    train_sign_sample = train[train['sign'] == sign]\n    n_frames = 0\n    n_left_hand = 0\n    n_right_hand = 0\n    n_face = 0\n    n_both_hands = 0\n    for _,row in train_sign_sample.iterrows():\n        df = pd.read_parquet(os.path.join(\"/kaggle/input/asl-signs\", row.path))\n        n_frames += df['frame'].nunique()\n        n_left_hand += np.sum(df[(df['type'] == 'left_hand') & (df['landmark_index'] == 0)]['x'].isnull() == False)\n        n_right_hand += np.sum(df[(df['type'] == 'right_hand') & (df['landmark_index'] == 0)]['x'].isnull() == False)\n        n_face += np.sum(df[(df['type'] == 'face') & (df['landmark_index'] == 0)]['x'].isnull() == False)\n        \n        df_both_hands = df[(df['type'] == 'left_hand') & (df['landmark_index'] == 0)].merge(\\\n                            df[(df['type'] == 'right_hand') & (df['landmark_index'] == 0)], on='frame', suffixes=('_left', '_right'))\n        n_both_hands += df_both_hands[(df_both_hands['x_left'].isnull() == False) &\\\n                                             (df_both_hands['x_right'].isnull() == False)]['frame'].count()\n            \n    return n_frames/len(train_sign_sample), n_left_hand/n_frames, n_right_hand/n_frames, n_both_hands/n_frames, n_face/n_frames\n","metadata":{"execution":{"iopub.status.busy":"2025-04-28T04:26:31.321865Z","iopub.execute_input":"2025-04-28T04:26:31.322726Z","iopub.status.idle":"2025-04-28T04:26:31.333494Z","shell.execute_reply.started":"2025-04-28T04:26:31.322690Z","shell.execute_reply":"2025-04-28T04:26:31.332219Z"},"_kg_hide-input":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for sign in ['cloud', 'thankyou', 'donkey', 'because', 'yellow', 'icecream']:\n    total_frames, pct_left, pct_right, pct_both, pct_face = get_details_per_sign(sign)\n    print(\"=\"*20, f\"{sign}\", \"=\"*20)\n    print(f\"Average Number of Frames per Sequence = {total_frames}\")\n    print(f\"Percent of Frames in which a body part exists: Left Hand: {pct_left*100:.02f} %, Right Hand: {pct_right*100:.02f} %, Both Hands: {pct_both*100:.02f} %, Face: {pct_face*100:.02f} %\")\n    print()","metadata":{"execution":{"iopub.status.busy":"2025-04-28T04:26:38.008682Z","iopub.execute_input":"2025-04-28T04:26:38.009939Z","iopub.status.idle":"2025-04-28T04:28:34.884494Z","shell.execute_reply.started":"2025-04-28T04:26:38.009892Z","shell.execute_reply":"2025-04-28T04:28:34.883468Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Update:** The organizers have said that the participants in the data are supposed to have used only one hand and that the signs in the data are doable with a single hand. https://www.kaggle.com/competitions/asl-signs/discussion/390715#2160782","metadata":{}},{"cell_type":"markdown","source":"# Modeling\n\nThis competition surely needs a sequential model, and a pretty light one since there's a lot of latency constraints imposed on it. \n> In this competition you will be submitting a TensorFlow Lite model file. The model must take one or more landmark frames as an input and return a float vector (the predicted probabilities of each sign class) as the output. Your model must be packaged into a submission.zip file and compatible with the TensorFlow Lite Runtime v2.9.1. You are welcome to train your model using the framework of your choice, as long as you convert the model checkpoint into the tflite format prior to submission.\n\n> Your model must also require less than **40 MB** in memory and perform inference with less than **100 milliseconds of latency** per video. Expect to see approximately **40,000 videos** in the test set. We allow an additional 10 minute buffer for loading the data and miscellaneous overhead.\n\nSo LSTMs and Transformers are surely going to be the standouts! However, the model size and latency requirements will prevent LLMs from dominating and will hence leave room for all sorts of models to be explored.","metadata":{}},{"cell_type":"markdown","source":"**Framework:** There are lots of easy guides to train in pytorch and convert and submit in tensorflow. So unless you are a tensorflow wizard, pytorch may be an easier route here.","metadata":{}},{"cell_type":"markdown","source":"# 28 April 2025. Sur mencoba untuk mencari sequence gerakan Sign Language Hello.","metadata":{}},{"cell_type":"markdown","source":"## 1. Cari Data untuk Satu Sign (“hello”)","metadata":{}},{"cell_type":"code","source":"# Kumpulan import jadi satu di sini\nimport pandas as pd \n# import plotly.express as px\nimport plotly.io as pio\nimport plotly.express as px\n# untuk animation\nimport matplotlib.pyplot as plt\nimport matplotlib.animation as animation\nfrom matplotlib.animation import FuncAnimation","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T05:50:44.729830Z","iopub.execute_input":"2025-04-28T05:50:44.730264Z","iopub.status.idle":"2025-04-28T05:50:44.736621Z","shell.execute_reply.started":"2025-04-28T05:50:44.730227Z","shell.execute_reply":"2025-04-28T05:50:44.735341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Seolah mau berdiri sendiri\n\n# Load data utamanya\ndf = pd.read_csv('/kaggle/input/asl-signs/train.csv')\n\n# ingin tahu total parquet\ntotal_parquet = df.shape[0]\nprint(f\"Total file Parquet di df: {total_parquet}\")\n\n# ingin tahu ada berapa banyak sign\nprint(df['sign'].unique().tolist())\n\n# contoh isinya\nprint(\"Isi df (data utama):\")\nprint(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T04:52:38.741651Z","iopub.execute_input":"2025-04-28T04:52:38.742056Z","iopub.status.idle":"2025-04-28T04:52:38.873431Z","shell.execute_reply.started":"2025-04-28T04:52:38.742024Z","shell.execute_reply":"2025-04-28T04:52:38.872317Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Ingin tahu berapa jumlah data signnya \n\n# kata yang mau difilter\nsign_name = \"hello\"\n# untuk semua participant untuk kata itu\nsign_df = df[df['sign'] == sign_name]\n\n# Print summary untuk semua participant\nprint(\n    f\"Jumlah data sign '{sign_name}': {len(sign_df)}\\n\\n\"\n    f\"Distribusi participant_id:\\n{sign_df['participant_id'].value_counts()}\\n\\n\"\n    # f\"Daftar path Parquet:\\n{sign_df['path'].tolist()}\"\n)\n\n# tertarik hanya untuk 1 participant saja\nparticipant_id = 61333  # Ganti dengan participant_id yang Kakak mau\n# filter sign_name = \"hello\" dan participant_id\nfiltered_df = df[(df['sign'] == sign_name) & (df['participant_id'] == participant_id)]\n\n# Print summary untuk satu participant_id saja\nprint(\n    f\"Jumlah data sign '{sign_name}' untuk participant '{participant_id}': {len(filtered_df)} 'rekaman.'\\n\\n\"\n)\n\nparquet_paths = filtered_df['path'].tolist()\nresult = \"Daftar path Parquet:\\n\" + \"\\n\".join(parquet_paths)\nprint(result)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T05:28:00.484505Z","iopub.execute_input":"2025-04-28T05:28:00.484957Z","iopub.status.idle":"2025-04-28T05:28:00.512348Z","shell.execute_reply.started":"2025-04-28T05:28:00.484923Z","shell.execute_reply":"2025-04-28T05:28:00.511002Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inginnya bisa tampilkan frekuensi jumlah rekaman per participant_id. Tapi gagal karena Kaggle envinroment","metadata":{"execution":{"iopub.status.busy":"2025-04-28T05:21:47.061389Z","iopub.execute_input":"2025-04-28T05:21:47.062593Z","iopub.status.idle":"2025-04-28T05:21:47.089277Z","shell.execute_reply.started":"2025-04-28T05:21:47.062516Z","shell.execute_reply":"2025-04-28T05:21:47.087939Z"}}},{"cell_type":"code","source":"# # Ini renderer paling aman untuk Kaggle:\n# # pio.renderers.default = \"notebook_connected\"\n# # Alternatif lain (kadang juga works): \n# pio.renderers.default = \"notebook\"\n# # Prepare data for plotly\n# freq = sign_df['participant_id'].value_counts().sort_index()\n# freq_df = freq.reset_index()\n# freq_df.columns = ['participant_id', 'frequency']\n\n# # Interactive bar chart\n# fig = px.bar(\n#     freq_df,\n#     x='participant_id',\n#     y='frequency',\n#     title=f'Frequency of participant_id for sign \\\"{sign_name}\\\"',\n#     labels={'participant_id': 'Participant ID', 'frequency': 'Frequency'},\n#     color='frequency',\n#     color_continuous_scale='Blues'\n# )\n# fig.update_layout(xaxis_tickangle=-45)\n# fig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T05:28:09.888881Z","iopub.execute_input":"2025-04-28T05:28:09.889318Z","iopub.status.idle":"2025-04-28T05:28:09.895027Z","shell.execute_reply.started":"2025-04-28T05:28:09.889281Z","shell.execute_reply":"2025-04-28T05:28:09.893583Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Ambil Satu Sequence (ID)","metadata":{}},{"cell_type":"code","source":"# Path folder landmark files dari dataset ASL Signs\nLANDMARK_FILES_DIR = \"/kaggle/input/asl-signs/train_landmark_files\"\n\n# Contoh: pilih ID peserta dan file Parquet yang ingin diakses\nparticipant_id = \"61333\"           # Ganti sesuai kebutuhan\nparquet_file = \"1474345895.parquet\"  # Ganti sesuai kebutuhan\n\n# Cek isi folder peserta\nparticipant_folder = os.path.join(LANDMARK_FILES_DIR, participant_id)\n# print(f\"Isi folder participant {participant_id}:\")\n# print(os.listdir(participant_folder))\n\n# Path lengkap file Parquet\nparquet_path = os.path.join(participant_folder, parquet_file)\n# print(f\"\\nPath file Parquet yang dipilih: {parquet_path}\")\n\n# Baca file Parquet\ndf_target = pd.read_parquet(parquet_path)\nprint(\"Jumlah baris (dan kolom):\", df_target.shape)\n\nprint(\"Frame terkecil:\", df_target['frame'].min())\nprint(\"Frame terbesar:\", df_target['frame'].max())\n\njumlah_frame = df_target['frame'].nunique()\nprint(\"Jumlah frame dalam video ini:\", jumlah_frame)\n\nbaris_per_frame = df_target['frame'].value_counts().sort_index()\nprint(\"Jumlah landmark per frame:\\n\", baris_per_frame)\n\nprint(\"\\nPreview isi file Parquet:\")\n# print(df_target.head())\nprint(df_target)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T05:47:04.506153Z","iopub.execute_input":"2025-04-28T05:47:04.506585Z","iopub.status.idle":"2025-04-28T05:47:04.537777Z","shell.execute_reply.started":"2025-04-28T05:47:04.506531Z","shell.execute_reply":"2025-04-28T05:47:04.536248Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Animasinya","metadata":{}},{"cell_type":"code","source":"# --- DataFrame sudah ada: df_target ---\n\nframes = sorted(df_target['frame'].unique())\n\ndef plot_landmarks(ax, df_frame):\n    ax.clear()\n    types = df_frame['type'].unique()\n    colors = {\n        'face': 'orange',\n        'left_hand': 'blue',\n        'right_hand': 'green',\n        'pose': 'red'\n    }\n    for t in types:\n        subset = df_frame[df_frame['type'] == t]\n        ax.scatter(subset['x'], -subset['y'], label=t, color=colors.get(t, 'black'), s=10)\n    ax.set_xlim(0, 1)\n    ax.set_ylim(-1, 0)\n    ax.legend()\n    ax.set_title(f\"Frame: {df_frame['frame'].iloc[0]}\")\n    ax.axis('off')\n\nfig, ax = plt.subplots(figsize=(5, 5))\n\ndef animate(i):\n    frame_num = frames[i]\n    df_frame = df_target[df_target['frame'] == frame_num]\n    plot_landmarks(ax, df_frame)\n\nani = FuncAnimation(fig, animate, frames=len(frames), interval=200, repeat=True)\nani.save('animasi_landmark.gif', writer='pillow')\nplt.close()\n\n# Tampilkan animasinya\nfrom IPython.display import Image\nImage(filename='animasi_landmark.gif')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T05:54:06.688286Z","iopub.execute_input":"2025-04-28T05:54:06.688715Z","iopub.status.idle":"2025-04-28T05:54:10.446696Z","shell.execute_reply.started":"2025-04-28T05:54:06.688679Z","shell.execute_reply":"2025-04-28T05:54:10.445631Z"}},"outputs":[],"execution_count":null}]}