diff --git a/normalization/blazepose_mapping.py b/normalization/blazepose_mapping.py index 666aba7..ad24b55 100644 --- a/normalization/blazepose_mapping.py +++ b/normalization/blazepose_mapping.py @@ -61,20 +61,25 @@ def map_blazepose_keypoint(column): return f"{mapped}_{hand}{suffix}" -def map_blazepose_df(df): +def map_blazepose_df(df, rename=True): + to_drop = [] + if rename: + renamings = {} + for column in df.columns: + mapped_column = map_blazepose_keypoint(column) + if mapped_column: + renamings[column] = mapped_column + else: + to_drop.append(column) + df = df.rename(columns=renamings) + for index, row in df.iterrows(): + sequence_size = len(row["leftEar_Y"]) lsx = row["leftShoulder_X"] rsx = row["rightShoulder_X"] lsy = row["leftShoulder_Y"] rsy = row["rightShoulder_Y"] - # convert all to list - lsx = lsx[1:-1].split(",") - rsx = rsx[1:-1].split(",") - lsy = lsy[1:-1].split(",") - rsy = rsy[1:-1].split(",") - sequence_size = len(lsx) - neck_x = [] neck_y = [] # Treat each element of the sequence (analyzed frame) individually @@ -84,4 +89,5 @@ def map_blazepose_df(df): df.loc[index, "neck_X"] = str(neck_x) df.loc[index, "neck_Y"] = str(neck_y) - return df + df.drop(columns=to_drop, inplace=True) + return df \ No newline at end of file diff --git a/normalization/main.py b/normalization/main.py index 9f4231f..2ed4891 100644 --- a/normalization/main.py +++ b/normalization/main.py @@ -5,30 +5,30 @@ import pandas as pd from normalization.hand_normalization import normalize_hands_full from normalization.body_normalization import normalize_body_full -DATASET_PATH = './data/wlasl' +DATASET_PATH = './data/processed' # Load the dataset -df = pd.read_csv(os.path.join(DATASET_PATH, "WLASL100_train.csv"), encoding="utf-8") +df = pd.read_csv(os.path.join(DATASET_PATH, "spoter_train.csv"), encoding="utf-8") print(df.head()) print(df.columns) # Retrieve metadata -video_size_heights = df["video_height"].to_list() -video_size_widths = df["video_width"].to_list() +# video_size_heights = df["video_height"].to_list() +# video_size_widths = df["video_width"].to_list() # Delete redundant (non-related) properties -del df["video_height"] -del df["video_width"] +# del df["video_height"] +# del df["video_width"] # Temporarily remove other relevant metadata labels = df["labels"].to_list() -video_fps = df["fps"].to_list() +signs = df["sign"].to_list() + del df["labels"] -del df["fps"] -del df["split"] -del df["video_id"] -del df["label_name"] -del df["length"] +del df["sign"] +del df["path"] +del df["participant_id"] +del df["sequence_id"] # Convert the strings into lists @@ -41,7 +41,7 @@ for column in df.columns: # Perform the normalizations df = normalize_hands_full(df) -df, invalid_row_indexes = normalize_body_full(df) +# df, invalid_row_indexes = normalize_body_full(df) # Clear lists of items from deleted rows # labels = [t for i, t in enumerate(labels) if i not in invalid_row_indexes] @@ -49,6 +49,6 @@ df, invalid_row_indexes = normalize_body_full(df) # Return the metadata back to the dataset df["labels"] = labels -df["fps"] = video_fps +df["sign"] = signs -df.to_csv(os.path.join(DATASET_PATH, "wlasl_train_norm.csv"), encoding="utf-8", index=False) +df.to_csv(os.path.join(DATASET_PATH, "spoter_train_norm.csv"), encoding="utf-8", index=False) diff --git a/notebooks/visualize_embeddings.ipynb b/notebooks/visualize_embeddings.ipynb index 4268c5a..3ad8c09 100644 --- a/notebooks/visualize_embeddings.ipynb +++ b/notebooks/visualize_embeddings.ipynb @@ -2,10 +2,19 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 22, "id": "8ef5cd92", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload\n", "%autoreload 2" @@ -13,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 23, "id": "78c4643a", "metadata": {}, "outputs": [], @@ -28,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 24, "id": "ffba4333", "metadata": {}, "outputs": [], @@ -38,7 +47,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 25, "id": "5bc81f71", "metadata": {}, "outputs": [], @@ -48,7 +57,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 49, "id": "3de8bcf2", "metadata": { "lines_to_next_cell": 0 @@ -64,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 50, "id": "91a045ba", "metadata": {}, "outputs": [], @@ -84,17 +93,17 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 51, "id": "bc50c296", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 7, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -115,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 52, "id": "82766a17", "metadata": {}, "outputs": [], @@ -129,7 +138,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 62, "id": "ead15a36", "metadata": {}, "outputs": [ @@ -139,7 +148,7 @@ "" ] }, - "execution_count": 9, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } @@ -151,7 +160,7 @@ "# checkpoint = torch.load(model.get_weights())\n", "\n", "\n", - "CHECKPOINT_PATH = \"../out-checkpoints/augment_rotate_75_x8/checkpoint_embed_18.pth\"\n", + "CHECKPOINT_PATH = \"../out-checkpoints/augment_rotate_75_x8/checkpoint_embed_187.pth\"\n", "checkpoint = torch.load(CHECKPOINT_PATH, map_location=device)\n", "\n", "\n", @@ -166,7 +175,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 71, "id": "20f8036d", "metadata": {}, "outputs": [], @@ -185,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 72, "id": "758716b6", "metadata": {}, "outputs": [], @@ -205,7 +214,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 73, "id": "f1527959", "metadata": {}, "outputs": [ @@ -222,7 +231,7 @@ ], "source": [ "dataloaders = {}\n", - "splits = ['train', 'val']\n", + "splits = ['test']\n", "dfs = {}\n", "for split in splits:\n", " split_set_path = op.join(dataset_folder, split_dataset_path.format(split))\n", @@ -255,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 74, "id": "3c3af5bf", "metadata": { "lines_to_next_cell": 0 @@ -264,10 +273,10 @@ { "data": { "text/plain": [ - "1220" + "2000" ] }, - "execution_count": 24, + "execution_count": 74, "metadata": {}, "output_type": "execute_result" } @@ -286,13 +295,13 @@ " # df['video_fn'] = df['video_id'].apply(lambda video_id: os.path.join(BASE_DATA_FOLDER, f'lsa/videos/{video_id}.mp4'))\n", " dfs[split] = df\n", "\n", - "df = pd.concat([dfs['train'].sample(20), dfs['val']]).reset_index(drop=True)\n", + "df = pd.concat([dfs['test']]).reset_index(drop=True)\n", "len(df)" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 75, "id": "dccbe1b9", "metadata": {}, "outputs": [], @@ -315,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 76, "id": "904298f0", "metadata": {}, "outputs": [], @@ -329,7 +338,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 77, "id": "42832f7c", "metadata": { "scrolled": false @@ -340,7 +349,7 @@ "text/html": [ "
\n", " \n", - " Loading BokehJS ...\n", + " Loading BokehJS ...\n", "
\n" ] }, @@ -349,7 +358,7 @@ }, { "data": { - "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\nconst JS_MIME_TYPE = 'application/javascript';\n const HTML_MIME_TYPE = 'text/html';\n const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n const CLASS_NAME = 'output_bokeh rendered_html';\n\n /**\n * Render data to the DOM node\n */\n function render(props, node) {\n const script = document.createElement(\"script\");\n node.appendChild(script);\n }\n\n /**\n * Handle when an output is cleared or removed\n */\n function handleClearOutput(event, handle) {\n const cell = handle.cell;\n\n const id = cell.output_area._bokeh_element_id;\n const server_id = cell.output_area._bokeh_server_id;\n // Clean up Bokeh references\n if (id != null && id in Bokeh.index) {\n Bokeh.index[id].model.document.clear();\n delete Bokeh.index[id];\n }\n\n if (server_id !== undefined) {\n // Clean up Bokeh references\n const cmd_clean = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n cell.notebook.kernel.execute(cmd_clean, {\n iopub: {\n output: function(msg) {\n const id = msg.content.text.trim();\n if (id in Bokeh.index) {\n Bokeh.index[id].model.document.clear();\n delete Bokeh.index[id];\n }\n }\n }\n });\n // Destroy server and session\n const cmd_destroy = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n cell.notebook.kernel.execute(cmd_destroy);\n }\n }\n\n /**\n * Handle when a new output is added\n */\n function handleAddOutput(event, handle) {\n const output_area = handle.output_area;\n const output = handle.output;\n\n // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n return\n }\n\n const toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n\n if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n // store reference to embed id on output_area\n output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n }\n if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n const bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n const script_attrs = bk_div.children[0].attributes;\n for (let i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n }\n\n function register_renderer(events, OutputArea) {\n\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n const toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[toinsert.length - 1]);\n element.append(toinsert);\n return toinsert\n }\n\n /* Handle when an output is cleared or removed */\n events.on('clear_output.CodeCell', handleClearOutput);\n events.on('delete.Cell', handleClearOutput);\n\n /* Handle when a new output is added */\n events.on('output_added.OutputArea', handleAddOutput);\n\n /**\n * Register the mime type and append_mime function with output_area\n */\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n /* Is output safe? */\n safe: true,\n /* Index of renderer in `output_area.display_order` */\n index: 0\n });\n }\n\n // register the mime type if in Jupyter Notebook environment and previously unregistered\n if (root.Jupyter !== undefined) {\n const events = require('base/js/events');\n const OutputArea = require('notebook/js/outputarea').OutputArea;\n\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n }\n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n const NB_LOAD_WARNING = {'data': {'text/html':\n \"
\\n\"+\n \"

\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"

\\n\"+\n \"
    \\n\"+\n \"
  • re-rerun `output_notebook()` to attempt to load from CDN again, or
  • \\n\"+\n \"
  • use INLINE resources instead, as so:
  • \\n\"+\n \"
\\n\"+\n \"\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"\\n\"+\n \"
\"}};\n\n function display_loaded() {\n const el = document.getElementById(\"1144\");\n if (el != null) {\n el.textContent = \"BokehJS is loading...\";\n }\n if (root.Bokeh !== undefined) {\n if (el != null) {\n el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(display_loaded, 100)\n }\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls == null || js_urls.length === 0) {\n run_callbacks();\n return null;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n\n function on_error(url) {\n console.error(\"failed to load \" + url);\n }\n\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n }\n\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.3.min.js\"];\n const css_urls = [];\n\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {\n }\n ];\n\n function run_inline_js() {\n if (root.Bokeh !== undefined || force === true) {\n for (let i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\nif (force === true) {\n display_loaded();\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n } else if (force !== true) {\n const cell = $(document.getElementById(\"1144\")).parents('.cell').data().cell;\n cell.output_area.append_execute_result(NB_LOAD_WARNING)\n }\n }\n\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(css_urls, js_urls, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));", + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\nconst JS_MIME_TYPE = 'application/javascript';\n const HTML_MIME_TYPE = 'text/html';\n const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n const CLASS_NAME = 'output_bokeh rendered_html';\n\n /**\n * Render data to the DOM node\n */\n function render(props, node) {\n const script = document.createElement(\"script\");\n node.appendChild(script);\n }\n\n /**\n * Handle when an output is cleared or removed\n */\n function handleClearOutput(event, handle) {\n const cell = handle.cell;\n\n const id = cell.output_area._bokeh_element_id;\n const server_id = cell.output_area._bokeh_server_id;\n // Clean up Bokeh references\n if (id != null && id in Bokeh.index) {\n Bokeh.index[id].model.document.clear();\n delete Bokeh.index[id];\n }\n\n if (server_id !== undefined) {\n // Clean up Bokeh references\n const cmd_clean = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n cell.notebook.kernel.execute(cmd_clean, {\n iopub: {\n output: function(msg) {\n const id = msg.content.text.trim();\n if (id in Bokeh.index) {\n Bokeh.index[id].model.document.clear();\n delete Bokeh.index[id];\n }\n }\n }\n });\n // Destroy server and session\n const cmd_destroy = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n cell.notebook.kernel.execute(cmd_destroy);\n }\n }\n\n /**\n * Handle when a new output is added\n */\n function handleAddOutput(event, handle) {\n const output_area = handle.output_area;\n const output = handle.output;\n\n // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n return\n }\n\n const toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n\n if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n // store reference to embed id on output_area\n output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n }\n if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n const bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n const script_attrs = bk_div.children[0].attributes;\n for (let i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n }\n\n function register_renderer(events, OutputArea) {\n\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n const toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[toinsert.length - 1]);\n element.append(toinsert);\n return toinsert\n }\n\n /* Handle when an output is cleared or removed */\n events.on('clear_output.CodeCell', handleClearOutput);\n events.on('delete.Cell', handleClearOutput);\n\n /* Handle when a new output is added */\n events.on('output_added.OutputArea', handleAddOutput);\n\n /**\n * Register the mime type and append_mime function with output_area\n */\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n /* Is output safe? */\n safe: true,\n /* Index of renderer in `output_area.display_order` */\n index: 0\n });\n }\n\n // register the mime type if in Jupyter Notebook environment and previously unregistered\n if (root.Jupyter !== undefined) {\n const events = require('base/js/events');\n const OutputArea = require('notebook/js/outputarea').OutputArea;\n\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n }\n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n const NB_LOAD_WARNING = {'data': {'text/html':\n \"
\\n\"+\n \"

\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"

\\n\"+\n \"
    \\n\"+\n \"
  • re-rerun `output_notebook()` to attempt to load from CDN again, or
  • \\n\"+\n \"
  • use INLINE resources instead, as so:
  • \\n\"+\n \"
\\n\"+\n \"\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"\\n\"+\n \"
\"}};\n\n function display_loaded() {\n const el = document.getElementById(\"1849\");\n if (el != null) {\n el.textContent = \"BokehJS is loading...\";\n }\n if (root.Bokeh !== undefined) {\n if (el != null) {\n el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(display_loaded, 100)\n }\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls == null || js_urls.length === 0) {\n run_callbacks();\n return null;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n\n function on_error(url) {\n console.error(\"failed to load \" + url);\n }\n\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n }\n\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.3.min.js\"];\n const css_urls = [];\n\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {\n }\n ];\n\n function run_inline_js() {\n if (root.Bokeh !== undefined || force === true) {\n for (let i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\nif (force === true) {\n display_loaded();\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n } else if (force !== true) {\n const cell = $(document.getElementById(\"1849\")).parents('.cell').data().cell;\n cell.output_area.append_execute_result(NB_LOAD_WARNING)\n }\n }\n\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(css_urls, js_urls, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));", "application/vnd.bokehjs_load.v0+json": "" }, "metadata": {}, @@ -373,7 +382,10 @@ " \n", " \n", "\"\"\"\n", - "cmap = LinearColorMapper(palette=\"Turbo256\", low=0, high=len(set_labels))\n", + "\n", + "# get labels\n", + "labels = df['sign'].values\n", + "cmap = LinearColorMapper(palette=\"Turbo256\", low=0, high=len(labels))\n", "\n", "output_notebook()\n", "# or \n", @@ -387,7 +399,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 82, "id": "ead4daf7", "metadata": { "scrolled": false @@ -395,23 +407,14 @@ "outputs": [ { "ename": "KeyError", - "evalue": "'label_name'", + "evalue": "21", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", - "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/pandas/core/indexes/base.py:3802\u001b[0m, in \u001b[0;36mIndex.get_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m 3801\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m-> 3802\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_engine\u001b[39m.\u001b[39;49mget_loc(casted_key)\n\u001b[1;32m 3803\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m \u001b[39mas\u001b[39;00m err:\n", - "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/pandas/_libs/index.pyx:138\u001b[0m, in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n", - "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/pandas/_libs/index.pyx:165\u001b[0m, in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n", - "File \u001b[0;32mpandas/_libs/hashtable_class_helper.pxi:5745\u001b[0m, in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n", - "File \u001b[0;32mpandas/_libs/hashtable_class_helper.pxi:5753\u001b[0m, in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n", - "\u001b[0;31mKeyError\u001b[0m: 'label_name'", - "\nThe above exception was the direct cause of the following exception:\n", - "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[29], line 5\u001b[0m\n\u001b[1;32m 1\u001b[0m column_data \u001b[39m=\u001b[39m \u001b[39mdict\u001b[39m(\n\u001b[1;32m 2\u001b[0m x\u001b[39m=\u001b[39mdf[\u001b[39m'\u001b[39m\u001b[39mtsne_x\u001b[39m\u001b[39m'\u001b[39m],\n\u001b[1;32m 3\u001b[0m y\u001b[39m=\u001b[39mdf[\u001b[39m'\u001b[39m\u001b[39mtsne_y\u001b[39m\u001b[39m'\u001b[39m],\n\u001b[1;32m 4\u001b[0m label\u001b[39m=\u001b[39mdf[\u001b[39m'\u001b[39m\u001b[39msign\u001b[39m\u001b[39m'\u001b[39m],\n\u001b[0;32m----> 5\u001b[0m label_desc\u001b[39m=\u001b[39mdf[\u001b[39m'\u001b[39;49m\u001b[39mlabel_name\u001b[39;49m\u001b[39m'\u001b[39;49m],\n\u001b[1;32m 6\u001b[0m split\u001b[39m=\u001b[39mdf[\u001b[39m'\u001b[39m\u001b[39msplit\u001b[39m\u001b[39m'\u001b[39m],\n\u001b[1;32m 7\u001b[0m video_id\u001b[39m=\u001b[39mdf[\u001b[39m'\u001b[39m\u001b[39mvideo_id\u001b[39m\u001b[39m'\u001b[39m]\n\u001b[1;32m 8\u001b[0m )\n\u001b[1;32m 10\u001b[0m \u001b[39mif\u001b[39;00m use_img_div:\n\u001b[1;32m 11\u001b[0m emb_videos \u001b[39m=\u001b[39m load_videos(df[\u001b[39m'\u001b[39m\u001b[39mvideo_fn\u001b[39m\u001b[39m'\u001b[39m])\n", - "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/pandas/core/frame.py:3807\u001b[0m, in \u001b[0;36mDataFrame.__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 3805\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcolumns\u001b[39m.\u001b[39mnlevels \u001b[39m>\u001b[39m \u001b[39m1\u001b[39m:\n\u001b[1;32m 3806\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_getitem_multilevel(key)\n\u001b[0;32m-> 3807\u001b[0m indexer \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mcolumns\u001b[39m.\u001b[39;49mget_loc(key)\n\u001b[1;32m 3808\u001b[0m \u001b[39mif\u001b[39;00m is_integer(indexer):\n\u001b[1;32m 3809\u001b[0m indexer \u001b[39m=\u001b[39m [indexer]\n", - "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/pandas/core/indexes/base.py:3804\u001b[0m, in \u001b[0;36mIndex.get_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m 3802\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_engine\u001b[39m.\u001b[39mget_loc(casted_key)\n\u001b[1;32m 3803\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m \u001b[39mas\u001b[39;00m err:\n\u001b[0;32m-> 3804\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mKeyError\u001b[39;00m(key) \u001b[39mfrom\u001b[39;00m \u001b[39merr\u001b[39;00m\n\u001b[1;32m 3805\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mTypeError\u001b[39;00m:\n\u001b[1;32m 3806\u001b[0m \u001b[39m# If we have a listlike key, _check_indexing_error will raise\u001b[39;00m\n\u001b[1;32m 3807\u001b[0m \u001b[39m# InvalidIndexError. Otherwise we fall through and re-raise\u001b[39;00m\n\u001b[1;32m 3808\u001b[0m \u001b[39m# the TypeError.\u001b[39;00m\n\u001b[1;32m 3809\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_check_indexing_error(key)\n", - "\u001b[0;31mKeyError\u001b[0m: 'label_name'" + "Cell \u001b[0;32mIn[82], line 12\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[39m# map labels to 0 to num_classes\u001b[39;00m\n\u001b[1;32m 11\u001b[0m label_to_id \u001b[39m=\u001b[39m {label: i \u001b[39mfor\u001b[39;00m i, label \u001b[39min\u001b[39;00m \u001b[39menumerate\u001b[39m(set_labels)}\n\u001b[0;32m---> 12\u001b[0m column_data[\u001b[39m'\u001b[39m\u001b[39mlabels\u001b[39m\u001b[39m'\u001b[39m] \u001b[39m=\u001b[39m [label_to_id[label] \u001b[39mfor\u001b[39;00m label \u001b[39min\u001b[39;00m column_data[\u001b[39m'\u001b[39m\u001b[39mlabel\u001b[39m\u001b[39m'\u001b[39m]]\n\u001b[1;32m 15\u001b[0m \u001b[39mif\u001b[39;00m use_img_div:\n\u001b[1;32m 16\u001b[0m emb_videos \u001b[39m=\u001b[39m load_videos(df[\u001b[39m'\u001b[39m\u001b[39mvideo_fn\u001b[39m\u001b[39m'\u001b[39m])\n", + "Cell \u001b[0;32mIn[82], line 12\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[39m# map labels to 0 to num_classes\u001b[39;00m\n\u001b[1;32m 11\u001b[0m label_to_id \u001b[39m=\u001b[39m {label: i \u001b[39mfor\u001b[39;00m i, label \u001b[39min\u001b[39;00m \u001b[39menumerate\u001b[39m(set_labels)}\n\u001b[0;32m---> 12\u001b[0m column_data[\u001b[39m'\u001b[39m\u001b[39mlabels\u001b[39m\u001b[39m'\u001b[39m] \u001b[39m=\u001b[39m [label_to_id[label] \u001b[39mfor\u001b[39;00m label \u001b[39min\u001b[39;00m column_data[\u001b[39m'\u001b[39m\u001b[39mlabel\u001b[39m\u001b[39m'\u001b[39m]]\n\u001b[1;32m 15\u001b[0m \u001b[39mif\u001b[39;00m use_img_div:\n\u001b[1;32m 16\u001b[0m emb_videos \u001b[39m=\u001b[39m load_videos(df[\u001b[39m'\u001b[39m\u001b[39mvideo_fn\u001b[39m\u001b[39m'\u001b[39m])\n", + "\u001b[0;31mKeyError\u001b[0m: 21" ] } ], @@ -422,22 +425,31 @@ " label=df['labels'],\n", " label_desc=df['sign'],\n", " split=df['split'],\n", - " video_id=df['video_id']\n", + " # video_id=df['video_id']\n", ")\n", "\n", + "# get unique labels\n", + "set_labels = list(set(labels))\n", + "\n", + "# map labels to 0 to num_classes\n", + "label_to_id = {label: i for i, label in enumerate(set_labels)}\n", + "column_data['labels'] = [label_to_id[label] for label in column_data['label']]\n", + "\n", + "\n", "if use_img_div:\n", " emb_videos = load_videos(df['video_fn'])\n", " column_data[\"videos\"] = emb_videos\n", "source = ColumnDataSource(data=column_data)\n", "\n", - "p.scatter('x', 'y',\n", - " size=10,\n", - " source=source,\n", - " fill_color={\"field\": 'label', \"transform\": cmap},\n", - " line_color={\"field\": 'label', \"transform\": cmap}, \n", - " #legend_label={\"field\": 'split', \"transform\": lambda x: df['split']},\n", - "# marker={\"field\": 'split'}\n", - " )\n", + "# scatter plot with for each label another color\n", + "p.scatter(x='x',\n", + " y='y',\n", + " source=source,\n", + " color={'field': 'label', 'transform': cmap},\n", + " legend_field='label_desc',\n", + " size=10,\n", + " alpha=0.5)\n", + "\n", "\n", "show(p)" ] diff --git a/preprocessing.py b/preprocessing.py index 34ac917..ac4b7f5 100644 --- a/preprocessing.py +++ b/preprocessing.py @@ -1,5 +1,5 @@ from argparse import ArgumentParser -from preprocessing.create_wlasl_landmarks_dataset import parse_create_args, create +from preprocessing.create_fingerspelling_dataset import parse_create_args, create from preprocessing.extract_mediapipe_landmarks import parse_extract_args, extract diff --git a/preprocessing/create_fingerspelling_dataset.py b/preprocessing/create_fingerspelling_dataset.py new file mode 100644 index 0000000..36ac643 --- /dev/null +++ b/preprocessing/create_fingerspelling_dataset.py @@ -0,0 +1,172 @@ +import os +import os.path as op +import json +import shutil + +import cv2 +import mediapipe as mp +import numpy as np +import pandas as pd +from utils import get_logger +from tqdm.auto import tqdm +from sklearn.model_selection import train_test_split +from normalization.blazepose_mapping import map_blazepose_df + +BASE_DATA_FOLDER = 'data/' + +mp_drawing = mp.solutions.drawing_utils +mp_drawing_styles = mp.solutions.drawing_styles +mp_hands = mp.solutions.hands +mp_holistic = mp.solutions.holistic +pose_landmarks = mp_holistic.PoseLandmark +hand_landmarks = mp_holistic.HandLandmark + + +def get_landmarks_names(): + ''' + Returns landmark names for mediapipe holistic model + ''' + pose_lmks = ','.join([f'{lmk.name.lower()}_x,{lmk.name.lower()}_y' for lmk in pose_landmarks]) + left_hand_lmks = ','.join([f'left_hand_{lmk.name.lower()}_x,left_hand_{lmk.name.lower()}_y' + for lmk in hand_landmarks]) + right_hand_lmks = ','.join([f'right_hand_{lmk.name.lower()}_x,right_hand_{lmk.name.lower()}_y' + for lmk in hand_landmarks]) + lmks_names = f'{pose_lmks},{left_hand_lmks},{right_hand_lmks}' + return lmks_names + + +def convert_to_str(arr, precision=6): + if isinstance(arr, np.ndarray): + values = [] + for val in arr: + if val == 0: + values.append('0') + else: + values.append(f'{val:.{precision}f}') + return f"[{','.join(values)}]" + else: + return str(arr) + + +def parse_create_args(parser): + parser.add_argument('--landmarks-dataset', '-lmks', required=True, + help='Path to folder with landmarks npy files. \ + You need to run `extract_mediapipe_landmarks.py` script first') + parser.add_argument('--dataset-folder', '-df', default='data/wlasl', + help='Path to folder where original `WLASL_v0.3.json` and `id_to_label.json` are stored. \ + Note that final CSV files will be saved in this folder too.') + parser.add_argument('--videos-folder', '-videos', default=None, + help='Path to folder with videos. If None, then no information of videos (fps, length, \ + width and height) will be stored in final csv file') + parser.add_argument('--num-classes', '-nc', default=100, type=int, help='Number of classes to use in WLASL dataset') + parser.add_argument('--create-new-split', action='store_true') + parser.add_argument('--test-size', '-ts', default=0.25, type=float, + help='Test split percentage size. Only required if --create-new-split is set') + + +# python3 preprocessing.py --landmarks-dataset=data/landmarks -videos data/wlasl/videos +def create(args): + logger = get_logger(__name__) + + landmarks_dataset = args.landmarks_dataset + videos_folder = args.videos_folder + dataset_folder = args.dataset_folder + num_classes = args.num_classes + test_size = args.test_size + + os.makedirs(dataset_folder, exist_ok=True) + + # shutil.copy(os.path.join(BASE_DATA_FOLDER, 'wlasl/id_to_label.json'), dataset_folder) + # shutil.copy(os.path.join(BASE_DATA_FOLDER, 'wlasl/WLASL_v0.3.json'), dataset_folder) + + # get files in landmarks_dataset folder + landmarks_files = os.listdir(landmarks_dataset) + + video_data = [] + for i, file in enumerate(tqdm(landmarks_files)): + + # split by ! + label = file.split('!')[0] + subset = file.split('!')[1].split('.')[0] + + # remove npy and set mp4 + video_id = file.replace('.npy', "") + + + video_dict = {'video_id': video_id, + 'label_name': label, + 'split': subset} + + if videos_folder is not None: + cap = cv2.VideoCapture(op.join(videos_folder, f'{video_id}.mp4')) + if not cap.isOpened(): + logger.warning(f'Video {video_id}.mp4 not found') + continue + width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) + height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) + fps = cap.get(cv2.CAP_PROP_FPS) + length = cap.get(cv2.CAP_PROP_FRAME_COUNT) / float(cap.get(cv2.CAP_PROP_FPS)) + video_info = {'video_width': width, + 'video_height': height, + 'fps': fps, + 'length': length} + video_dict.update(video_info) + video_data.append(video_dict) + + df_video = pd.DataFrame(video_data) + video_ids = df_video['video_id'].unique() + lmks_data = [] + lmks_names = get_landmarks_names().split(',') + + # get labels from df_video + labels = df_video['label_name'].unique() + # map labels to ids + label_to_id = {label: i for i, label in enumerate(labels)} + + # add label_id column to df_video + df_video['labels'] = df_video['label_name'].map(label_to_id) + + # export to json file as id to label + id_to_label = {i: label for label, i in label_to_id.items()} + with open(op.join(dataset_folder, 'id_to_label.json'), 'w') as f: + json.dump(id_to_label, f, indent=4) + + for video_id in video_ids: + lmk_fn = op.join(landmarks_dataset, f'{video_id}.npy') + if not op.exists(lmk_fn): + logger.warning(f'{lmk_fn} file not found. Skipping') + continue + lmk = np.load(lmk_fn).T + lmks_dict = {'video_id': video_id} + for lmk_, name in zip(lmk, lmks_names): + lmks_dict[name] = lmk_ + lmks_data.append(lmks_dict) + + df_lmks = pd.DataFrame(lmks_data) + print(df_lmks) + df = pd.merge(df_video, df_lmks) + print(df) + aux_columns = ['split', 'video_id', 'labels', 'label_name'] + if videos_folder is not None: + aux_columns += ['video_width', 'video_height', 'fps', 'length'] + df_aux = df[aux_columns] + df = map_blazepose_df(df) + df = pd.concat([df, df_aux], axis=1) + if args.create_new_split: + df_train, df_test = train_test_split(df, test_size=test_size, stratify=df['labels'], random_state=42) + else: + print(df['split'].unique()) + df_train = df[(df['split'] == 'train') | (df['split'] == 'val')] + df_test = df[df['split'] == 'test'] + + print(f'Num classes: {num_classes}') + print(df_train['labels'].value_counts()) + assert set(df_train['labels'].unique()) == set(df_test['labels'].unique( + )), 'The labels for train and test dataframe are different. We recommend to download the dataset again, or to use \ + the --create-new-split flag' + for split, df_split in zip(['train', 'val'], + [df_train, df_test]): + fn_out = op.join(dataset_folder, f'fingerspelling_{split}.csv') + (df_split.reset_index(drop=True) + .applymap(convert_to_str) + .to_csv(fn_out, index=False)) diff --git a/preprocessing/create_google_asl_landmarks_dataset.py b/preprocessing/create_google_asl_landmarks_dataset.py index 4a4beda..c128356 100644 --- a/preprocessing/create_google_asl_landmarks_dataset.py +++ b/preprocessing/create_google_asl_landmarks_dataset.py @@ -4,6 +4,8 @@ import pandas as pd from tqdm.auto import tqdm import json +from normalization.blazepose_mapping import map_blazepose_df + def create(train_landmark_files, train_csv, dataset_folder, test_size): os.makedirs(dataset_folder, exist_ok=True) @@ -17,15 +19,15 @@ def create(train_landmark_files, train_csv, dataset_folder, test_size): mapping = { 'pose_0': 'nose', 'pose_1': 'leftEye', - 'pose_2': 'rightEye', - 'pose_3': 'leftEar', - 'pose_4': 'rightEar', - 'pose_5': 'leftShoulder', - 'pose_6': 'rightShoulder', - 'pose_7': 'leftElbow', - 'pose_8': 'rightElbow', - 'pose_9': 'leftWrist', - 'pose_10': 'rightWrist', + 'pose_4': 'rightEye', + 'pose_7': 'leftEar', + 'pose_8': 'rightEar', + 'pose_11': 'leftShoulder', + 'pose_12': 'rightShoulder', + 'pose_13': 'leftElbow', + 'pose_14': 'rightElbow', + 'pose_15': 'leftWrist', + 'pose_16': 'rightWrist', 'left_hand_0': 'wrist_left', 'left_hand_1': 'thumbCMC_left', @@ -77,7 +79,7 @@ def create(train_landmark_files, train_csv, dataset_folder, test_size): columns.append(f'{v}_X') columns.append(f'{v}_Y') - for _, row in tqdm(train_df.head(6000).iterrows(), total=6000): + for _, row in tqdm(train_df.head(10000).iterrows(), total=10000): path, participant_id, sequence_id, sign = row['path'], row['participant_id'], row['sequence_id'], row['sign'] parquet_file = os.path.join(train_landmark_files, str(participant_id), f"{sequence_id}.parquet") @@ -136,6 +138,7 @@ def create(train_landmark_files, train_csv, dataset_folder, test_size): video_data.append(new_landmark_data) video_data = pd.concat(video_data, axis=0, ignore_index=True) + video_data = map_blazepose_df(video_data, rename=False) video_data.to_csv(os.path.join(dataset_folder, 'spoter.csv'), index=False) train_landmark_files = 'data/train_landmark_files' diff --git a/preprocessing/create_wlasl_landmarks_dataset.py b/preprocessing/create_wlasl_landmarks_dataset.py index c1e4d5c..53d5cf4 100644 --- a/preprocessing/create_wlasl_landmarks_dataset.py +++ b/preprocessing/create_wlasl_landmarks_dataset.py @@ -110,6 +110,7 @@ def create(args): 'length': length} video_dict.update(video_info) video_data.append(video_dict) + df_video = pd.DataFrame(video_data) video_ids = df_video['video_id'].unique() lmks_data = [] @@ -126,9 +127,7 @@ def create(args): lmks_data.append(lmks_dict) df_lmks = pd.DataFrame(lmks_data) - print(df_lmks) df = pd.merge(df_video, df_lmks) - print(df) aux_columns = ['split', 'video_id', 'labels', 'label_name'] if videos_folder is not None: aux_columns += ['video_width', 'video_height', 'fps', 'length'] diff --git a/preprocessing/extract_mediapipe_landmarks.py b/preprocessing/extract_mediapipe_landmarks.py index 6d63076..f421919 100644 --- a/preprocessing/extract_mediapipe_landmarks.py +++ b/preprocessing/extract_mediapipe_landmarks.py @@ -132,6 +132,12 @@ def extract(args): ret, image_orig = cap.read() height, width = image_orig.shape[:2] landmarks_video = [] + + # make sure fps is 20 by determining the number of frames to be skipped + frame_rate = int(cap.get(cv2.CAP_PROP_FPS)) + frame_skip = (frame_rate // 20) - 1 + + with tqdm(total=int(cap.get(cv2.CAP_PROP_FRAME_COUNT))) as pbar: with mp_holistic.Holistic( static_image_mode=False, @@ -145,6 +151,9 @@ def extract(args): print(e) landmarks = get_landmarks(image_orig, holistic, debug=True) ret, image_orig = cap.read() + for _ in range(frame_skip): + ret, image_orig = cap.read() + pbar.update(1) landmarks_video.append(landmarks) pbar.update(1) landmarks_video = np.vstack(landmarks_video) diff --git a/preprocessing/split_dataset.py b/preprocessing/split_dataset.py index 17e1f55..3fa13c7 100644 --- a/preprocessing/split_dataset.py +++ b/preprocessing/split_dataset.py @@ -8,7 +8,6 @@ dataset = "data/processed/spoter.csv" # read the dataset df = pd.read_csv(dataset) -df = map_blazepose_df(df) with open("data/sign_to_prediction_index_map.json", "r") as f: sign_to_prediction_index_max = json.load(f) diff --git a/requirements.txt b/requirements.txt index 2308b4c..a7d1285 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ pandas bokeh==2.4.3 boto3>=1.9 -clearml==1.6.4 ipywidgets==8.0.4 matplotlib==3.5.3 mediapipe==0.8.11 @@ -9,6 +8,7 @@ notebook==6.5.2 opencv-python==4.6.0.66 plotly==5.11.0 scikit-learn==1.0.2 +clearml==1.10.3 torch torchvision tqdm==4.54.1 diff --git a/train.py b/train.py index 76fabfe..1d27a79 100644 --- a/train.py +++ b/train.py @@ -15,7 +15,7 @@ from torchvision import transforms from torch.utils.data import DataLoader from pathlib import Path import copy - +import numpy as np from datasets import CzechSLRDataset, SLREmbeddingDataset, collate_fn_triplet_padd, collate_fn_padd from models import SPOTER, SPOTER_EMBEDDINGS, train_epoch, evaluate, train_epoch_embedding, \ train_epoch_embedding_online, evaluate_embedding @@ -32,7 +32,7 @@ except ImportError: pass -PROJECT_NAME = "spoter" +PROJECT_NAME = "SpoterEmbedding" CLEARML = "clearml" diff --git a/train.sh b/train.sh index fe68689..4572863 100755 --- a/train.sh +++ b/train.sh @@ -1,22 +1,21 @@ #!/bin/sh python -m train \ --save_checkpoints_every 10 \ - --experiment_name "augment_rotate_75_x8" \ + --experiment_name "basic" \ --epochs 300 \ --optimizer "ADAM" \ - --lr 0.001 \ + --lr 0.0001 \ --batch_size 16 \ - --dataset_name "processed" \ + --dataset_name "GoogleWLASL" \ --training_set_path "spoter_train.csv" \ --validation_set_path "spoter_test.csv" \ --vector_length 32 \ --epoch_iters -1 \ - --scheduler_factor 0 \ - --hard_triplet_mining "in_batch" \ + --scheduler_factor 0.2 \ + --hard_triplet_mining "None" \ --filter_easy_triplets \ - --triplet_loss_margin 1 \ + --triplet_loss_margin 2 \ --dropout 0.2 \ - --augmentations_prob=0.75 \ - --hard_mining_scheduler_triplets_threshold=0 \ - --normalize_embeddings \ - --num_classes 100 \ \ No newline at end of file + --tracker=clearml \ + --dataset_loader=clearml \ + --dataset_project="SpoterEmbedding" diff --git a/visualize_data.ipynb b/visualize_data.ipynb new file mode 100644 index 0000000..7586303 --- /dev/null +++ b/visualize_data.ipynb @@ -0,0 +1,1632 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from augmentations.augment import __preprocess_row_sign" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 path\n", + "1 participant_id\n", + "2 sequence_id\n", + "3 sign\n", + "4 labels\n", + "5 nose_X\n", + "6 nose_Y\n", + "7 leftEye_X\n", + "8 leftEye_Y\n", + "9 rightEye_X\n", + "10 rightEye_Y\n", + "11 leftEar_X\n", + "12 leftEar_Y\n", + "13 rightEar_X\n", + "14 rightEar_Y\n", + "15 leftShoulder_X\n", + "16 leftShoulder_Y\n", + "17 rightShoulder_X\n", + "18 rightShoulder_Y\n", + "19 leftElbow_X\n", + "20 leftElbow_Y\n", + "21 rightElbow_X\n", + "22 rightElbow_Y\n", + "23 leftWrist_X\n", + "24 leftWrist_Y\n", + "25 rightWrist_X\n", + "26 rightWrist_Y\n", + "27 wrist_left_X\n", + "28 wrist_left_Y\n", + "29 thumbCMC_left_X\n", + "30 thumbCMC_left_Y\n", + "31 thumbMP_left_X\n", + "32 thumbMP_left_Y\n", + "33 thumbIP_left_X\n", + "34 thumbIP_left_Y\n", + "35 thumbTip_left_X\n", + "36 thumbTip_left_Y\n", + "37 indexMCP_left_X\n", + "38 indexMCP_left_Y\n", + "39 indexPIP_left_X\n", + "40 indexPIP_left_Y\n", + "41 indexDIP_left_X\n", + "42 indexDIP_left_Y\n", + "43 indexTip_left_X\n", + "44 indexTip_left_Y\n", + "45 middleMCP_left_X\n", + "46 middleMCP_left_Y\n", + "47 middlePIP_left_X\n", + "48 middlePIP_left_Y\n", + "49 middleDIP_left_X\n", + "50 middleDIP_left_Y\n", + "51 middleTip_left_X\n", + "52 middleTip_left_Y\n", + "53 ringMCP_left_X\n", + "54 ringMCP_left_Y\n", + "55 ringPIP_left_X\n", + "56 ringPIP_left_Y\n", + "57 ringDIP_left_X\n", + "58 ringDIP_left_Y\n", + "59 ringTip_left_X\n", + "60 ringTip_left_Y\n", + "61 littleMCP_left_X\n", + "62 littleMCP_left_Y\n", + "63 littlePIP_left_X\n", + "64 littlePIP_left_Y\n", + "65 littleDIP_left_X\n", + "66 littleDIP_left_Y\n", + "67 littleTip_left_X\n", + "68 littleTip_left_Y\n", + "69 wrist_right_X\n", + "70 wrist_right_Y\n", + "71 thumbCMC_right_X\n", + "72 thumbCMC_right_Y\n", + "73 thumbMP_right_X\n", + "74 thumbMP_right_Y\n", + "75 thumbIP_right_X\n", + "76 thumbIP_right_Y\n", + "77 thumbTip_right_X\n", + "78 thumbTip_right_Y\n", + "79 indexMCP_right_X\n", + "80 indexMCP_right_Y\n", + "81 indexPIP_right_X\n", + "82 indexPIP_right_Y\n", + "83 indexDIP_right_X\n", + "84 indexDIP_right_Y\n", + "85 indexTip_right_X\n", + "86 indexTip_right_Y\n", + "87 middleMCP_right_X\n", + "88 middleMCP_right_Y\n", + "89 middlePIP_right_X\n", + "90 middlePIP_right_Y\n", + "91 middleDIP_right_X\n", + "92 middleDIP_right_Y\n", + "93 middleTip_right_X\n", + "94 middleTip_right_Y\n", + "95 ringMCP_right_X\n", + "96 ringMCP_right_Y\n", + "97 ringPIP_right_X\n", + "98 ringPIP_right_Y\n", + "99 ringDIP_right_X\n", + "100 ringDIP_right_Y\n", + "101 ringTip_right_X\n", + "102 ringTip_right_Y\n", + "103 littleMCP_right_X\n", + "104 littleMCP_right_Y\n", + "105 littlePIP_right_X\n", + "106 littlePIP_right_Y\n", + "107 littleDIP_right_X\n", + "108 littleDIP_right_Y\n", + "109 littleTip_right_X\n", + "110 littleTip_right_Y\n", + "111 neck_X\n", + "112 neck_Y\n" + ] + } + ], + "source": [ + "df = pd.read_csv('data/processed/spoter_train.csv')\n", + "for i, e in enumerate(df.columns):\n", + " print(i, e)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "# dataset = 'data/processed/spoter_train.csv'\n", + "\n", + "def plot(dataset):\n", + " df = pd.read_csv(dataset)\n", + " # get columns 0 - 26\n", + " # df = df.iloc[:, 5:27]\n", + "\n", + "\n", + " # df = df.iloc[:, 27:69]\n", + " # df = df.iloc[:, 69:]\n", + "\n", + " if 'wlasl' in dataset:\n", + " df = df.iloc[:, :108]\n", + "\n", + " else:\n", + " df = df.iloc[:, 5:]\n", + "\n", + "\n", + " # get first row\n", + " row = df.iloc[0]\n", + "\n", + " # use matplotlib to create a scatter plot (the columns are X, Y, X, Y, X, Y, X, Y)\n", + " import matplotlib.pyplot as plt\n", + " import ast\n", + "\n", + " def __process_row(coords):\n", + " coords = coords.tolist()\n", + "\n", + " new_coords = [] # string to list\n", + "\n", + " for x in coords:\n", + " new_coords.append(ast.literal_eval(x))\n", + "\n", + " coords = [x[2] for x in new_coords]\n", + "\n", + " return [float(x) for x in coords]\n", + "\n", + "\n", + " # extract X and Y coordinates from the row\n", + " x_coords = row.iloc[::2] # columns 0, 2, 4, 6, ...\n", + " y_coords = row.iloc[1::2] # columns 1, 3, 5, 7, ...\n", + "\n", + " x_coords = __process_row(x_coords)\n", + " y_coords = __process_row(y_coords)\n", + "\n", + " # create a scatter plot\n", + " plt.scatter(x_coords, y_coords)\n", + "\n", + " # add axis labels and a title\n", + " plt.xlabel('X')\n", + " plt.ylabel('Y')\n", + " plt.title('Scatter plot of X and Y coordinates')\n", + "\n", + " # show the plot\n", + " plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA/40lEQVR4nO3de1yUZf7/8feAwniAQUIOGilaVuQBjyym2QHDdCkrN9NKs4NpZiW137JcSa1ws8zyrJm2tqZb2dnwQLltxS6mUh7SzDDNBE8JqCskc/3+8MesI6CAMAM3r+fjMY/i4rpnPvc1g/Oe677va2zGGCMAAACL8PF2AQAAAFWJcAMAACyFcAMAACyFcAMAACyFcAMAACyFcAMAACyFcAMAACyFcAMAACyFcAMAACyFcANYwNVXX62rr77a22W4ycnJ0YABA3TBBRfIZrNp2rRp3i6pSrRs2VJ33323t8uoEc583e3atUs2m02LFi3yWk2ARLhBDbZp0yYNGDBALVq0kN1uV/PmzdW7d29Nnz692h5zyZIlpb4J//rrr3rmmWeUmZlZbY/tDcePH9czzzyjtWvXVvl9jxkzRitXrtTYsWO1ePFi9enTp9R+q1evls1m04QJE0r8LisrSw0bNtSAAQOqvL7qsnTpUtlsNs2dO7fU348cOVL169fXt99+6+HKrOP555/X+++/7+0yUJMZoAb66quvjJ+fn7n44ovNpEmTzPz588348ePN9ddfb1q3bl1tj9uvXz/TokWLEu3r1q0zkszChQur7bHPR69evUyvXr0qvN2BAweMJJOcnFzlNYWFhZk77rijXH0HDx5s/P39zfbt293a+/TpYwIDA83evXurvL7KatGihRk6dOhZ+/Tp08cEBQWZ7Oxst/b//Oc/xsfHx/z5z3+uxgo958zXndPpNP/973/NyZMnq/VxGzVqdM7nAHVbPW+HK6A0zz33nBwOh9atW6egoCC33+3fv987RVWDY8eOqVGjRt4uo1rs37+/xHNXlpdfflmffvqpRowYoc8++0zSqRmQ1NRUvfrqq2rWrFk1Vlr1Zs+erSuuuEJjxozRkiVLJElFRUV64IEHdNFFF+mZZ57xboHlVNHXp81mk91ur8aKgHLydroCSnPppZeaq6++utz9Fy9ebLp27WoaNGhggoKCTM+ePc3KlStdv3///fdN3759TUREhPHz8zOtWrUyEydOdPuE2atXLyPJ7daiRQvz+eefl2jXGbM4//73v01CQoIJDAw0DRo0MFdddZX58ssv3WpMTk42ksyWLVvMoEGDTFBQkImJiSlznxYuXGgkmX/+859m+PDhJjg42AQEBJi77rrLHD582K1vaTM3OTk55p577jGhoaHG39/ftG/f3ixatMj1+6ysrFL361yzODt37jQDBgwwTZo0MQ0aNDCxsbHm448/LlH3mbdzmTdvnpFkFi1aZH777TcTHh5uunbtaoqKis657a5du8zIkSNNmzZtjN1uN8HBwWbAgAEmKyvLrV9xbV9++aUZM2aMCQkJMQ0bNjT9+/c3+/fvd+vrdDrNpEmTTPPmzU2DBg3M1VdfbTZv3lyumRtjjHnhhReMJLNq1SpjjDFTp041ksynn356zm2NOfWauuGGG0xQUJBp2LChadeunZk2bZpbn7S0NNOjRw/TsGFD43A4zI033mi2bt1a4r42bNhg+vTpYwICAkyjRo3Mtddea9LT00sdm7Vr15qRI0eapk2bmqCgINfv586da1q1amXsdrvp2rWr+eKLL0q87opfU6f/bQwdOtQ0atTI/PLLL+amm24yjRo1MiEhIeaxxx4rMcMzZcoUExcXZ4KDg43dbjedOnUyb7/9tluf0l5bpz8fv/zyixk2bJgJDQ01fn5+Jjo62ixYsKDEmLz66qsmOjra9W9G586dzd///vcynw/ULoQb1EjXX3+9CQgIMJs2bTpn32eeecZIMt27dzdTpkwxr7zyihk8eLB54oknXH369+9vbrvtNjNlyhQze/Zs86c//clIMo8//rirz6pVq0xMTIwJCQkxixcvNosXLzbvvfeeyc7ONhMnTjSSzPDhw12/27lzpzHm1BuMn5+fiYuLMy+99JJ5+eWXTfv27Y2fn5/5z3/+47r/4nATHR1tbrrpJjNr1iwzc+bMMver+M2mXbt2pmfPnubVV181o0aNMj4+Puaqq64yTqfT1ffMN5njx4+byy+/3NSvX9+MGTPGvPrqq6Znz55GkusN8ujRo2b27NlGkrn55ptd+/Xtt9+WWVN2drYJCwszAQEB5umnnzZTp041HTp0MD4+Pmb58uXGmFPhZ/HixUaS6d27t+t+z8XpdJorr7zShISEmEGDBhlfX1+zYcOGc25njDFvv/226dChgxk/fryZN2+eeeqpp0yTJk1MixYtzLFjx0qMaceOHc21115rpk+fbh577DHj6+trbrvtNrf7HDdunJFk+vbta2bMmGHuuece06xZMxMSElKucPP777+bDh06mNatW5sdO3aYxo0bm9tvv71c+7Nq1Srj5+dnWrRoYZKTk83s2bPNww8/bOLj4119Vq9eberVq2fatGljXnjhBTNhwgQTEhJimjRp4hbqNm/ebBo1amQiIiLMpEmTzOTJk01UVJTx9/c3//73v0uMTXR0tOnVq5eZPn26mTx5sjHGmNdee831N/bqq6+aRx991AQFBZlWrVqVK9zY7XZzxRVXmHvuucfMnj3b3HrrrUaSmTVrltt+X3jhhebBBx80M2bMMFOnTjXdunUzktzC8+LFi42/v7/p2bOn67X19ddfG2NOvT4vvPBCExkZaSZOnGhmz55tbrzxRiPJvPzyy677KA7SAwYMMHPnzjWvvPKKuffee83DDz9crucHNR/hBjXSqlWrjK+vr/H19TVxcXHm//7v/8zKlStNYWGhW78dO3YYHx8fc/PNN5f4hH/6m//x48dLPMYDDzxgGjZsaE6cOOFqq+g5N06n01xyySUmISGhxONFRUWZ3r17u9qKw82gQYPKNQbFbzadO3d22+/iGYEPPvjA1XZmuJk2bZqRZN58801XW2FhoYmLizONGzc2eXl5xpiKn3Pz6KOPGknmX//6l6stPz/fREVFmZYtW7o9B5LMqFGjynW/xTZv3mzq169vJJlHH3203NuV9vymp6cbSeZvf/ubq614TOPj492erzFjxhhfX19z5MgRY4wx+/fvN35+fqZfv35u/Z566qkSMwVnU3yOTXBwcKnn4JTm5MmTJioqyrRo0cL89ttvbr87vZaYmBgTGhpqDh065Gr79ttvjY+PjxkyZIirrX///sbPz88Vxo0x5tdffzUBAQHmqquucrUVj02PHj3cZlQKCwtNaGioiYmJMQUFBa724oBQnnAjyUycONFtXzp27Gg6d+7s1nbm81hYWGjatm1rrr32Wrf2ss65uffee01ERIQ5ePCgW/vtt99uHA6H6/5vuukmc8UVV5TYHtbB1VKokXr37q309HTdeOON+vbbb/XCCy8oISFBzZs314cffujq9/7778vpdGr8+PHy8XF/OdtsNtf/N2jQwPX/+fn5OnjwoHr27Knj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fLGefvppTZo0ST179tQtt9wif39/rVu3Ts2aNVNKSoq6d++uJk2aaOjQoXr44Ydls9m0ePHiUsNT586dtWzZMiUlJalr165q3LixEhMT1bp1awUFBWnOnDkKCAhQo0aNFBsbq6ioKL322mu64YYbdMUVV2jYsGFq3ry59u7dq88//1yBgYH66KOPzmssCgsLdd111+m2227T9u3bNWvWLPXo0UM33nhjmdsMHz5cc+fO1d13363169erZcuWeuedd/TVV19p2rRpCggIkHTqEER0dLSWLVumNm3aKDg4WG3bti3zfI8nn3xSb731lm644QY9/PDDCg4O1htvvKGsrCy9++67Jc7xKY/169dr5syZGjVqlNt5Kc2bN9fEiROVlJSkd999V7feemuZ9/HHP/5RixcvlsPhUHR0tNLT07VmzRpdcMEFFa5HOjUL8PjjjyslJUV//OMf1bdvX23cuFGffvqpQkJCKnWf5eXj46PZs2crMTFRMTExGjZsmCIiIrRt2zZt2bJFK1eulHTqUOgNN9yguLg43Xvvvfrvf/+r6dOny+FwuC0S+Oyzz2r16tXq0aOHHnzwQdWrV09z585VQUGBXnjhhXPWU79+fT377LN64IEHdO2112rgwIHKysrSwoULy33OTXn069dPU6dOVZ8+fTR48GDt379fM2fO1MUXX6zvvvvOrW/nzp21Zs0aTZ06Vc2aNVNUVJRiY2M1efJkff7554qNjdX999+v6OhoHT58WBs2bNCaNWt0+PBhSadOVg8PD9eVV16psLAwff/995oxY4b69evn+ttALeelq7SAs/r000/NPffcYy677DLTuHFj11cxjB492uTk5JTo//rrr5uOHTsaf39/06RJE9OrVy+zevVq1++/+uor84c//ME0aNDANGvWzHVpuSTz+eefu/odPXrUDB482AQFBbkW8Sv2wQcfmOjoaFOvXr0Sl7tu3LjR3HLLLeaCCy4w/v7+pkWLFua2224zaWlprj7Fl4IfOHCgXGNw5iJ+TZo0MY0bNzZ33HGH2+W/xpS9iN+wYcNMSEiI8fPzM+3atSv16yO+/vpr07lzZ+Pn51ehRfyCgoKM3W433bp1c1uHpJjKcSn4yZMnTadOnUyzZs1Mbm5uqb+PiYkxF154ocnPzy/zfn777TfXvjZu3NgkJCSYbdu2lVhwr3hM161b57Z98UKNp78WioqKzIQJE0xERESlFvE712Oey5dffml69+7tWnivffv2Zvr06W591qxZY6688krToEEDExgYaBITE8tcxC8hIcE0btzYNGzY0FxzzTWutWHKW+esWbNc6+N06dKlwov4nan47+F0CxYsMJdcconx9/c3l112mVm4cGGp/bZt22auuuoq06BBgxKX5ufk5JhRo0aZyMhIU79+fRMeHm6uu+46M2/ePFefuXPnmquuusr199q6dWvz5z//udTXIGonmzGcQQfURIsWLdKwYcO0bt26cl9pAwDgnBsAAGAxhBsAAGAphBsAAGApnHMDAAAshZkbAABgKYQbAABgKXVuET+n06lff/1VAQEBFV56HgAAeIcxRvn5+WrWrNk5Fwytc+Hm119/Ped3qQAAgJppz549uvDCC8/ap86Fm+Kltffs2XPO71QBAAA1Q15eniIjI8v1FRl1LtwUH4oKDAwk3AAAUMuU55QSTigGAACWQrgBAACWQrgBAACWQrgBAACW4tVw88UXXygxMVHNmjWTzWbT+++/X+5tv/rqK9WrV08xMTHVVh8AAKh9vBpujh07pg4dOmjmzJkV2u7IkSMaMmSIrrvuumqqDAAA1FZevRT8hhtu0A033FDh7UaMGKHBgwfL19e3QrM9AADA+mrdOTcLFy7UTz/9pOTk5HL1LygoUF5entsNAABYV60KNzt27NCTTz6pN998U/XqlW/SKSUlRQ6Hw3XjqxcAALC2WhNuioqKNHjwYE2YMEFt2rQp93Zjx45Vbm6u67Znz55qrBIAgNqnyGmUvvOQPsjcq/Sdh1TkNN4u6bzUmq9fyM/P1zfffKONGzfqoYceknTqG76NMapXr55WrVqla6+9tsR2/v7+8vf393S5AADUCqmb92nCR1u1L/eEqy3CYVdyYrT6tI3wYmWVV2vCTWBgoDZt2uTWNmvWLH322Wd65513FBUV5aXKAAConVI379PINzfozHma7NwTGvnmBs2+s1OtDDheDTdHjx7Vjz/+6Po5KytLmZmZCg4O1kUXXaSxY8dq7969+tvf/iYfHx+1bdvWbfvQ0FDZ7fYS7QAA4OyKnEYTPtpaIthIkpFkkzTho63qHR0uX59zf1llTeLVc26++eYbdezYUR07dpQkJSUlqWPHjho/frwkad++fdq9e7c3SwQAwJIysg67HYo6k5G0L/eEMrIOe66oKmIzxtTus4YqKC8vTw6HQ7m5uQoMDPR2OQAAeMUHmXv1yNLMc/Z75fYY3RTTvPoLOoeKvH/XmqulAABA1QkNsFdpv5qEcAMAQB3ULSpYEQ67yjqbxqZTV011iwr2ZFlVgnADAEAd5OtjU3JitCSVCDjFPycnRte6k4klwg0AAHVWn7YRmn1nJ4U73A89hTvstfYycKkWrXMDAACqXp+2EeodHa6MrMPan39CoQGnDkXVxhmbYoQbAADqOF8fm+JaX+DtMqoMh6UAAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAIClEG4AAICl1PN2AQBqjyKnUUbWYe3PP6HQALu6RQXL18fm7bIAwA3hBkC5pG7epwkfbdW+3BOutgiHXcmJ0erTNsKLlQGAOw5LATin1M37NPLNDW7BRpKyc09o5JsblLp5n5cqA4CSCDcAzqrIaTTho60ypfyuuG3CR1tV5CytBwB4HuEGwFllZB0uMWNzOiNpX+4JZWQd9lxRAHAWhBsAZ7U/v+xgU5l+AFDdCDcAzio0wF6l/QCguhFuAJxVt6hgRTjsKuuCb5tOXTXVLSrYk2UBQJkINwDOytfHpuTEaEkqEXCKf05OjGa9GwA1BuEGwDn1aRuh2Xd2UrjD/dBTuMOu2Xd2Yp0bADUKi/gBKJc+bSPUOzqcFYoB1HiEGwDl5utjU1zrC7xdBgCcFYelAACApRBuAACApRBuAACApXg13HzxxRdKTExUs2bNZLPZ9P7775+1//Lly9W7d281bdpUgYGBiouL08qVKz1TLAAAqBW8Gm6OHTumDh06aObMmeXq/8UXX6h3795asWKF1q9fr2uuuUaJiYnauHFjNVcKAABqC5sxpkZ8la/NZtN7772n/v37V2i7K664QgMHDtT48ePL1T8vL08Oh0O5ubkKDAysRKUAAMDTKvL+XasvBXc6ncrPz1dwcNnLvhcUFKigoMD1c15enidKAwAAXlKrTyh+8cUXdfToUd12221l9klJSZHD4XDdIiMjPVghAADwtFobbpYsWaIJEyboH//4h0JDQ8vsN3bsWOXm5rpue/bs8WCVAADA02rlYamlS5fqvvvu09tvv634+Piz9vX395e/v7+HKgMAAN5W62Zu3nrrLQ0bNkxvvfWW+vXr5+1yAABADePVmZujR4/qxx9/dP2clZWlzMxMBQcH66KLLtLYsWO1d+9e/e1vf5N06lDU0KFD9corryg2NlbZ2dmSpAYNGsjhcHhlHwAAQM3i1Zmbb775Rh07dlTHjh0lSUlJSerYsaPrsu59+/Zp9+7drv7z5s3TyZMnNWrUKEVERLhujzzyiFfqBwAANU+NWefGU1jnBgCA2qci79+17pwbAACAsyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAASyHcAAAAS/FquPniiy+UmJioZs2ayWaz6f333z/nNmvXrlWnTp3k7++viy++WIsWLar2OgEAwLkVOY3Sdx7SB5l7lb7zkIqcxit11PPKo/5/x44dU4cOHXTPPffolltuOWf/rKws9evXTyNGjNDf//53paWl6b777lNERIQSEhI8UDEAAChN6uZ9mvDRVu3LPeFqi3DYlZwYrT5tIzxai80Y451YdQabzab33ntP/fv3L7PPE088oU8++USbN292td1+++06cuSIUlNTy/U4eXl5cjgcys3NVWBg4PmWDQBAnZe6eZ9GvrlBZwYK2///7+w7O513wKnI+3etOucmPT1d8fHxbm0JCQlKT08vc5uCggLl5eW53QAAQNUochpN+GhriWAjydU24aOtHj1EVavCTXZ2tsLCwtzawsLClJeXp//+97+lbpOSkiKHw+G6RUZGeqJUAADqhIysw26Hos5kJO3LPaGMrMMeq6lWhZvKGDt2rHJzc123PXv2eLskAAAsY39+2cGmMv2qgldPKK6o8PBw5eTkuLXl5OQoMDBQDRo0KHUbf39/+fv7e6I8AADqnNAAe5X2qwq1auYmLi5OaWlpbm2rV69WXFyclyoCAKBu6xYVrAiH3XXy8JlsOnXVVLeoYI/V5NVwc/ToUWVmZiozM1PSqUu9MzMztXv3bkmnDikNGTLE1X/EiBH66aef9H//93/atm2bZs2apX/84x8aM2aMN8oHzqmmrPkAANXF18em5MRoSSoRcIp/Tk6Mlq9PWfGn6nn1sNQ333yja665xvVzUlKSJGno0KFatGiR9u3b5wo6khQVFaVPPvlEY8aM0SuvvKILL7xQr732GmvcoEaqSWs+AEB16tM2QrPv7FTi37zwur7Ojaewzg08wRNrPgBATVPkNMrIOqz9+ScUGnDqUFRVzdhU5P27Vp1QDNQG51rzwaZTaz70jg736DQtAFQ3Xx+b4lpf4O0yatcJxUBtUBPXfACAuoRwA1SxmrjmAwDUJYQboIrVxDUfAKAuIdwAVawmrvkAAHUJ4QaoYjVxzQcAqEsIN0A1KF7zIdzhfugp3GHnMnAAqGZcCg5Ukz5tI9Q7Orza1nwAAJSOcANUo5qy5gMA1CUclgIAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJZCuAEAAJbi9XAzc+ZMtWzZUna7XbGxscrIyDhr/2nTpunSSy9VgwYNFBkZqTFjxujEiRMeqhYAANR0Xg03y5YtU1JSkpKTk7VhwwZ16NBBCQkJ2r9/f6n9lyxZoieffFLJycn6/vvvtWDBAi1btkxPPfWUhysHAAA1lVfDzdSpU3X//fdr2LBhio6O1pw5c9SwYUO9/vrrpfb/+uuvdeWVV2rw4MFq2bKlrr/+eg0aNOicsz0AAKDu8Fq4KSws1Pr16xUfH/+/Ynx8FB8fr/T09FK36d69u9avX+8KMz/99JNWrFihvn37lvk4BQUFysvLc7sBAADrquetBz548KCKiooUFhbm1h4WFqZt27aVus3gwYN18OBB9ejRQ8YYnTx5UiNGjDjrYamUlBRNmDChSmsHAAA1l9dPKK6ItWvX6vnnn9esWbO0YcMGLV++XJ988okmTZpU5jZjx45Vbm6u67Znzx4PVgwAADzNazM3ISEh8vX1VU5Ojlt7Tk6OwsPDS93mL3/5i+666y7dd999kqR27drp2LFjGj58uJ5++mn5+JTMav7+/vL396/6HQAAADWS12Zu/Pz81LlzZ6WlpbnanE6n0tLSFBcXV+o2x48fLxFgfH19JUnGmOorFgAA1Bpem7mRpKSkJA0dOlRdunRRt27dNG3aNB07dkzDhg2TJA0ZMkTNmzdXSkqKJCkxMVFTp05Vx44dFRsbqx9//FF/+ctflJiY6Ao5AACgbvNquBk4cKAOHDig8ePHKzs7WzExMUpNTXWdZLx79263mZpx48bJZrNp3Lhx2rt3r5o2barExEQ999xz3toFAABQw9hMHTuek5eXJ4fDodzcXAUGBnq7HAAAUA4Vef+uVVdLAQAAnAvhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWEq5w82vv/5anXUAAABUiXKHmyuuuEJLliypzloAAADOW7nDzXPPPacHHnhAf/rTn3T48OHqrAkAAKDSyh1uHnzwQX333Xc6dOiQoqOj9dFHH1VnXQAAAJVSryKdo6Ki9Nlnn2nGjBm65ZZbdPnll6tePfe72LBhQ5UWCAAAUBEVCjeS9PPPP2v58uVq0qSJbrrpphLhBgAAwJsqlEzmz5+vxx57TPHx8dqyZYuaNm1aXXUBAABUSrnDTZ8+fZSRkaEZM2ZoyJAh1VkTAABApZU73BQVFem7777ThRdeWJ31AAAAnJdyh5vVq1dXZx0AAABVgq9fAAAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAluL1cDNz5ky1bNlSdrtdsbGxysjIOGv/I0eOaNSoUYqIiJC/v7/atGmjFStWeKhaAABQ05X7W8Grw7Jly5SUlKQ5c+YoNjZW06ZNU0JCgrZv367Q0NAS/QsLC9W7d2+FhobqnXfeUfPmzfXzzz8rKCjI88UDAIAayWaMMd568NjYWHXt2lUzZsyQJDmdTkVGRmr06NF68sknS/SfM2eOpkyZom3btql+/fqVesy8vDw5HA7l5uYqMDDwvOoHAACeUZH3b68dliosLNT69esVHx//v2J8fBQfH6/09PRSt/nwww8VFxenUaNGKSwsTG3bttXzzz+voqIiT5UNAABqOK8dljp48KCKiooUFhbm1h4WFqZt27aVus1PP/2kzz77THfccYdWrFihH3/8UQ8++KB+//13JScnl7pNQUGBCgoKXD/n5eVV3U4AAFDDFDmNMrIOa3/+CYUG2NUtKli+PjZvl+VRXj3npqKcTqdCQ0M1b948+fr6qnPnztq7d6+mTJlSZrhJSUnRhAkTPFwpAACel7p5nyZ8tFX7ck+42iIcdiUnRqtP2wgvVuZZXjssFRISIl9fX+Xk5Li15+TkKDw8vNRtIiIi1KZNG/n6+rraLr/8cmVnZ6uwsLDUbcaOHavc3FzXbc+ePVW3EwBgAUVOo/Sdh/RB5l6l7zykIqfXTsXEeUjdvE8j39zgFmwkKTv3hEa+uUGpm/d5qTLP89rMjZ+fnzp37qy0tDT1799f0qmZmbS0ND300EOlbnPllVdqyZIlcjqd8vE5lct++OEHRUREyM/Pr9Rt/P395e/vXy37AAC1HZ/0raHIaTTho60qLZYaSTZJEz7aqt7R4XXiEJVX17lJSkrS/Pnz9cYbb+j777/XyJEjdezYMQ0bNkySNGTIEI0dO9bVf+TIkTp8+LAeeeQR/fDDD/rkk0/0/PPPa9SoUd7aBaDO4tN+7ccnfevIyDpc4nk8nZG0L/eEMrIOe64oL/LqOTcDBw7UgQMHNH78eGVnZysmJkapqamuk4x3797tmqGRpMjISK1cuVJjxoxR+/bt1bx5cz3yyCN64oknvLULQJ3Ep/3aj0/61rI/v+xgU5l+tZ1X17nxBta5Ac5P8af9M//hKH77m31nJwJOLZC+85AGzf/3Ofu9df8fFNf6Ag9UhPNRF57PWrHODYDa51yf9qVTn/Y5RFXz8UnfWrpFBSvCYVdZc2w2nZpd7RYV7MmyvIZwA6DcOK5vHaEB9irtB+/y9bEpOTFakkoEnOKfkxOj68whRsINgHLj07518Enfevq0jdDsOzsp3OEeSMMd9jp3uLhWLeIHwLv4tG8dxZ/0R765QTbJ7VBjXfykbxV92kaod3R4nV+hmJkbAOXGp31r4ZO+Nfn62BTX+gLdFNNcca0vqHPBRmLmBkAF8GnfevikDyviUnAAFcY6NwA8rSLv38zcAKgwPu0DqMkINwAqpfi4PgDUNJxQDAAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALIVwAwAALKVGhJuZM2eqZcuWstvtio2NVUZGRrm2W7p0qWw2m/r371+9BQIAgFrD6+Fm2bJlSkpKUnJysjZs2KAOHTooISFB+/fvP+t2u3bt0uOPP66ePXt6qFIAAFAbeD3cTJ06Vffff7+GDRum6OhozZkzRw0bNtTrr79e5jZFRUW64447NGHCBLVq1cqD1QIAgJrOq+GmsLBQ69evV3x8vKvNx8dH8fHxSk9PL3O7iRMnKjQ0VPfee+85H6OgoEB5eXluNwAAYF1eDTcHDx5UUVGRwsLC3NrDwsKUnZ1d6jZffvmlFixYoPnz55frMVJSUuRwOFy3yMjI864bAADUXF4/LFUR+fn5uuuuuzR//nyFhISUa5uxY8cqNzfXdduzZ081VwkAALypnjcfPCQkRL6+vsrJyXFrz8nJUXh4eIn+O3fu1K5du5SYmOhqczqdkqR69epp+/btat26tds2/v7+8vf3r4bqAQBATeTVmRs/Pz917txZaWlprjan06m0tDTFxcWV6H/ZZZdp06ZNyszMdN1uvPFGXXPNNcrMzOSQEwAA8O7MjSQlJSVp6NCh6tKli7p166Zp06bp2LFjGjZsmCRpyJAhat68uVJSUmS329W2bVu37YOCgiSpRDsAAKibvB5uBg4cqAMHDmj8+PHKzs5WTEyMUlNTXScZ7969Wz4+terUIAAA4EU2Y4zxdhGelJeXJ4fDodzcXAUGBnq7HAAAUA4Vef9mSgQAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFgK4QYAAFhKPW8XAADwniKnUUbWYe3PP6HQALu6RQXL18fm7bKqhJX3DWdHuAGAOip18z5N+Gir9uWecLVFOOxKToxWn7YRXqzs/Fl533BuHJYCgDoodfM+jXxzg9ubvyRl557QyDc3KHXzPi9Vdv6svG8oH8INAFSRIqdR+s5D+iBzr9J3HlKR03i7pFIVOY0mfLRVpVVX3Dbho601tv6zsfK+ofw4LAUAVaA2HQbJyDpcYlbjdEbSvtwTysg6rLjWF3iusCpg5X1D+TFzAwDnqbYdBtmfX/abf2X61SRW3jeUH+EGAM5DbTwMEhpgr9J+NYmV9w3lR7gBgPNQkcMgNUW3qGBFOOwq66Jom04dUusWFezJsqqElfcN5Ue4AYDzUBsPg/j62JScGC1JJUJA8c/JidG1ck0YK+8byo9wAwDnobYeBunTNkKz7+ykcId7XeEOu2bf2alaToL21NVk3tg31CxcLQUA56H4MEh27olSz7ux6dSbak08DNKnbYR6R4d7ZBVfT19N5sl9Q81jM8bUnLPcPCAvL08Oh0O5ubkKDAz0djkALKD4ailJbgGn+G30fGYLrPAVAsXjc+abTVWMD+qOirx/M3MDAOep+DDImTMT4ec5M1Gb1s4py7muJrPp1NVkvaPDa11oQ81FuAGAKlDVh0HKmu0oXjuntsx2sKgevIFwAwBVxNfHViVv0Faa7aiNV5Oh9uNqKQCoYWrj2jllqa1Xk6F2I9wAQA1jpdkOFtWDNxBuAKCGsdJsB4vqwRsINwBQw1httoNF9eBpnFAMADVM8WzHyDc3yKbS186pbbMdLKoHT6oRMzczZ85Uy5YtZbfbFRsbq4yMjDL7zp8/Xz179lSTJk3UpEkTxcfHn7U/ANRGVpztKL6a7KaY5oprfQHBBtXG6zM3y5YtU1JSkubMmaPY2FhNmzZNCQkJ2r59u0JDQ0v0X7t2rQYNGqTu3bvLbrfrr3/9q66//npt2bJFzZs398IeAED1YLYDqByvf/1CbGysunbtqhkzZkiSnE6nIiMjNXr0aD355JPn3L6oqEhNmjTRjBkzNGTIkHP25+sXAACofSry/u3Vw1KFhYVav3694uPjXW0+Pj6Kj49Xenp6ue7j+PHj+v333xUcXPqJdQUFBcrLy3O7AQAA6/JquDl48KCKiooUFhbm1h4WFqbs7Oxy3ccTTzyhZs2auQWk06WkpMjhcLhukZGR5103AACouWrECcWVNXnyZC1dulTvvfee7PbS13sYO3ascnNzXbc9e/Z4uEoAAOBJXj2hOCQkRL6+vsrJyXFrz8nJUXh4+Fm3ffHFFzV58mStWbNG7du3L7Ofv7+//P39q6ReAABQ83l15sbPz0+dO3dWWlqaq83pdCotLU1xcXFlbvfCCy9o0qRJSk1NVZcuXTxRKgAAqCW8fil4UlKShg4dqi5duqhbt26aNm2ajh07pmHDhkmShgwZoubNmyslJUWS9Ne//lXjx4/XkiVL1LJlS9e5OY0bN1bjxo29th8AAKBm8Hq4GThwoA4cOKDx48crOztbMTExSk1NdZ1kvHv3bvn4/G+Cafbs2SosLNSAAQPc7ic5OVnPPPOMJ0sHAAA1kNfXufE01rkBAKD2qTXr3AAAAFQ1wg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALAUwg0AALCUet4uwCrmrdmq59dkuX5+Kj5Kw+OjvVgRAAB1U42YuZk5c6Zatmwpu92u2NhYZWRknLX/22+/rcsuu0x2u13t2rXTihUrPFRp6Vo++YlbsJGk59dkqeWTn3ipIgAA6i6vh5tly5YpKSlJycnJ2rBhgzp06KCEhATt37+/1P5ff/21Bg0apHvvvVcbN25U//791b9/f23evNnDlZ9yrgBDwAEAwLNsxhjjzQJiY2PVtWtXzZgxQ5LkdDoVGRmp0aNH68knnyzRf+DAgTp27Jg+/vhjV9sf/vAHxcTEaM6cOed8vLy8PDkcDuXm5iowMPC8aj/zUFRZOEQFAMD5qcj7t1dnbgoLC7V+/XrFx8e72nx8fBQfH6/09PRSt0lPT3frL0kJCQll9i8oKFBeXp7braqUJ9hUpB8AADh/Xg03Bw8eVFFRkcLCwtzaw8LClJ2dXeo22dnZFeqfkpIih8PhukVGRlZN8QAAoEby+jk31W3s2LHKzc113fbs2ePtkgAAQDXyargJCQmRr6+vcnJy3NpzcnIUHh5e6jbh4eEV6u/v76/AwEC3W1V5Kj6qSvsBAIDz59Vw4+fnp86dOystLc3V5nQ6lZaWpri4uFK3iYuLc+svSatXry6zf3Uq70nCnEwMAIDneP2wVFJSkubPn6833nhD33//vUaOHKljx45p2LBhkqQhQ4Zo7Nixrv6PPPKIUlNT9dJLL2nbtm165pln9M033+ihhx7ySv27Jvc7r98DAICq5fUVigcOHKgDBw5o/Pjxys7OVkxMjFJTU10nDe/evVs+Pv/LYN27d9eSJUs0btw4PfXUU7rkkkv0/vvvq23btt7aBe2a3I8VigEAqCG8vs6Np1XlOjcAAMAzas06NwAAAFWNcAMAACyFcAMAACyFcAMAACyFcAMAACyFcAMAACyFcAMAACyFcAMAACyFcAMAACzF61+/4GnFCzLn5eV5uRIAAFBexe/b5flihToXbvLz8yVJkZGRXq4EAABUVH5+vhwOx1n71LnvlnI6nfr1118VEBAgm81Wpfedl5enyMhI7dmzp05/bxXjwBgUYxxOYRwYg2KMwymVGQdjjPLz89WsWTO3L9QuTZ2bufHx8dGFF15YrY8RGBhYp1+0xRgHxqAY43AK48AYFGMcTqnoOJxrxqYYJxQDAABLIdwAAABLIdxUIX9/fyUnJ8vf39/bpXgV48AYFGMcTmEcGINijMMp1T0Ode6EYgAAYG3M3AAAAEsh3AAAAEsh3AAAAEsh3AAAAEsh3FTQzJkz1bJlS9ntdsXGxiojI+Os/d9++21ddtllstvtateunVasWOGhSqtPRcZgy5YtuvXWW9WyZUvZbDZNmzbNc4VWs4qMw/z589WzZ081adJETZo0UXx8/DlfO7VFRcZh+fLl6tKli4KCgtSoUSPFxMRo8eLFHqy2+lT034ZiS5culc1mU//+/au3QA+oyBgsWrRINpvN7Wa32z1YbfWp6GvhyJEjGjVqlCIiIuTv7682bdrUufeKq6++usTrwWazqV+/fpV7cINyW7p0qfHz8zOvv/662bJli7n//vtNUFCQycnJKbX/V199ZXx9fc0LL7xgtm7dasaNG2fq169vNm3a5OHKq05FxyAjI8M8/vjj5q233jLh4eHm5Zdf9mzB1aSi4zB48GAzc+ZMs3HjRvP999+bu+++2zgcDvPLL794uPKqVdFx+Pzzz83y5cvN1q1bzY8//mimTZtmfH19TWpqqocrr1oVHYdiWVlZpnnz5qZnz57mpptu8kyx1aSiY7Bw4UITGBho9u3b57plZ2d7uOqqV9FxKCgoMF26dDF9+/Y1X375pcnKyjJr1641mZmZHq68alV0HA4dOuT2Wti8ebPx9fU1CxcurNTjE24qoFu3bmbUqFGun4uKikyzZs1MSkpKqf1vu+02069fP7e22NhY88ADD1RrndWpomNwuhYtWlgm3JzPOBhjzMmTJ01AQIB54403qqtEjzjfcTDGmI4dO5px48ZVR3keU5lxOHnypOnevbt57bXXzNChQ2t9uKnoGCxcuNA4HA4PVec5FR2H2bNnm1atWpnCwkJPlegR5/tvw8svv2wCAgLM0aNHK/X4HJYqp8LCQq1fv17x8fGuNh8fH8XHxys9Pb3UbdLT0936S1JCQkKZ/Wu6yoyBFVXFOBw/fly///67goODq6vMane+42CMUVpamrZv366rrrqqOkutVpUdh4kTJyo0NFT33nuvJ8qsVpUdg6NHj6pFixaKjIzUTTfdpC1btnii3GpTmXH48MMPFRcXp1GjRiksLExt27bV888/r6KiIk+VXeWq4t/IBQsW6Pbbb1ejRo0qVQPhppwOHjyooqIihYWFubWHhYUpOzu71G2ys7Mr1L+mq8wYWFFVjMMTTzyhZs2alQi/tUllxyE3N1eNGzeWn5+f+vXrp+nTp6t3797VXW61qcw4fPnll1qwYIHmz5/viRKrXWXG4NJLL9Xrr7+uDz74QG+++aacTqe6d++uX375xRMlV4vKjMNPP/2kd955R0VFRVqxYoX+8pe/6KWXXtKzzz7riZKrxfn+G5mRkaHNmzfrvvvuq3QNde5bwQFvmzx5spYuXaq1a9da5gTKiggICFBmZqaOHj2qtLQ0JSUlqVWrVrr66qu9XZpH5Ofn66677tL8+fMVEhLi7XK8Ji4uTnFxca6fu3fvrssvv1xz587VpEmTvFiZZzmdToWGhmrevHny9fVV586dtXfvXk2ZMkXJycneLs8rFixYoHbt2qlbt26Vvg/CTTmFhITI19dXOTk5bu05OTkKDw8vdZvw8PAK9a/pKjMGVnQ+4/Diiy9q8uTJWrNmjdq3b1+dZVa7yo6Dj4+PLr74YklSTEyMvv/+e6WkpNTacFPRcdi5c6d27dqlxMREV5vT6ZQk1atXT9u3b1fr1q2rt+gqVhX/NtSvX18dO3bUjz/+WB0lekRlxiEiIkL169eXr6+vq+3yyy9Xdna2CgsL5efnV601V4fzeT0cO3ZMS5cu1cSJE8+rBg5LlZOfn586d+6stLQ0V5vT6VRaWprbp4/TxcXFufWXpNWrV5fZv6arzBhYUWXH4YUXXtCkSZOUmpqqLl26eKLUalVVrwen06mCgoLqKNEjKjoOl112mTZt2qTMzEzX7cYbb9Q111yjzMxMRUZGerL8KlEVr4WioiJt2rRJERER1VVmtavMOFx55ZX68ccfXQFXkn744QdFRETUymAjnd/r4e2331ZBQYHuvPPO8yuiUqch11FLly41/v7+ZtGiRWbr1q1m+PDhJigoyHX54l133WWefPJJV/+vvvrK1KtXz7z44ovm+++/N8nJyZa4FLwiY1BQUGA2btxoNm7caCIiIszjjz9uNm7caHbs2OGtXagSFR2HyZMnGz8/P/POO++4Xe6Yn5/vrV2oEhUdh+eff96sWrXK7Ny502zdutW8+OKLpl69emb+/Pne2oUqUdFxOJMVrpaq6BhMmDDBrFy50uzcudOsX7/e3H777cZut5stW7Z4axeqREXHYffu3SYgIMA89NBDZvv27ebjjz82oaGh5tlnn/XWLlSJyv5N9OjRwwwcOPC8H59wU0HTp083F110kfHz8zPdunUz//73v12/69Wrlxk6dKhb/3/84x+mTZs2xs/Pz1xxxRXmk08+8XDFVa8iY5CVlWUklbj16tXL84VXsYqMQ4sWLUodh+TkZM8XXsUqMg5PP/20ufjii43dbjdNmjQxcXFxZunSpV6ouupV9N+G01kh3BhTsTF49NFHXX3DwsJM3759zYYNG7xQddWr6Gvh66+/NrGxscbf39+0atXKPPfcc+bkyZMerrrqVXQctm3bZiSZVatWnfdj24wx5vzmfgAAAGoOzrkBAACWQrgBAACWQrgBAACWQrgBAACWQrgBAACWQrgBAACWQrgBAACWQrgBAACWQrgBUKsVFRWpe/fuuuWWW9zac3NzFRkZqaefftpLlQHwFlYoBlDr/fDDD4qJidH8+fN1xx13SJKGDBmib7/9VuvWrau1X0AIoHIINwAs4dVXX9UzzzyjLVu2KCMjQ3/605+0bt06dejQwdulAfAwwg0ASzDG6Nprr5Wvr682bdqk0aNHa9y4cd4uC4AXEG4AWMa2bdt0+eWXq127dtqwYYPq1avn7ZIAeAEnFAOwjNdff10NGzZUVlaWfvnlF2+XA8BLmLkBYAlff/21evXqpVWrVunZZ5+VJK1Zs0Y2m83LlQHwNGZuANR6x48f1913362RI0fqmmuu0YIFC5SRkaE5c+Z4uzQAXsDMDYBa75FHHtGKFSv07bffqmHDhpKkuXPn6vHHH9emTZvUsmVL7xYIwKMINwBqtX/+85+67rrrtHbtWvXo0cPtdwkJCTp58iSHp4A6hnADAAAshXNuAACApRBuAACApRBuAACApRBuAACApRBuAACApRBuAACApRBuAACApRBuAACApRBuAACApRBuAACApRBuAACApRBuAACApfw/DIN97LSlbXgAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot('data/wlasl/WLASL100_train.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAABA5klEQVR4nO3deVyVZf7/8TeggBsgoYBGbmWFG4pKlEsZhumXssbJtNJsscyspGbSNHGpdFrMctdKGxvTqWw313IaiwYTrcx0TDGtBLcC1BTlXL8//HHGIyDnIGe7eT0fj/N4xM11n/M59zl0v72u+7ruAGOMEQAAgEUEersAAACAqkS4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4ASzg6quv1tVXX+3tMhzk5eWpX79+uuCCCxQQEKBp06Z5u6Qq0bRpU915553eLsMnnP292717twICArRw4UKv1QRIhBv4sO+++079+vVTkyZNFBoaqsaNG6tnz56aPn26215z8eLFZZ6Ef/31V40fP16bN29222t7w7FjxzR+/HitW7euyp975MiRWrlypUaPHq1FixapV69eZbZbvXq1AgICNGHChFK/y8nJUe3atdWvX78qr89dlixZooCAAM2dO7fM3w8bNkw1a9bUN9984+HKrOOZZ57Re++95+0y4MsM4IO++OILExwcbC6++GIzadIkM3/+fDNu3Dhz3XXXmRYtWrjtdfv06WOaNGlSavuGDRuMJLNgwQK3vfb56N69u+nevbvL+x04cMBIMhkZGVVeU3R0tLntttucajtw4EATEhJitm/f7rC9V69eJiwszPzyyy9VXl9lNWnSxAwePPicbXr16mUiIiJMbm6uw/b//Oc/JjAw0PzlL39xY4Wec/b3zmazmT/++MOcOnXKra9bp06dCj8DVG81vB2ugLI8/fTTCg8P14YNGxQREeHwu/3793unKDc4evSo6tSp4+0y3GL//v2lPrvyvPjii/rkk090//3369NPP5V0ugdkxYoVevnll9WoUSM3Vlr1Zs+erVatWmnkyJFavHixJKm4uFj33XefLrroIo0fP967BTrJ1e9nQECAQkND3VgR4CRvpyugLJdeeqm5+uqrnW6/aNEi06lTJ1OrVi0TERFhunbtalauXGn//XvvvWd69+5tYmNjTXBwsGnevLmZOHGiw78wu3fvbiQ5PJo0aWI+++yzUtt1Vi/OV199ZVJTU01YWJipVauW6datm1m/fr1DjRkZGUaS+f77782AAQNMRESESUhIKPc9LViwwEgy//rXv8zQoUNNZGSkqVevnrnjjjvM4cOHHdqW1XOTl5dn7rrrLtOwYUMTEhJi2rZtaxYuXGj/fU5OTpnvq6JenJ07d5p+/fqZ+vXrm1q1apmkpCTz0Ucflar77EdF5s2bZySZhQsXmt9++83ExMSYTp06meLi4gr33b17txk2bJhp2bKlCQ0NNZGRkaZfv34mJyfHoV1JbevXrzcjR440UVFRpnbt2qZv375m//79Dm1tNpuZNGmSady4salVq5a5+uqrzZYtW5zquTHGmGeffdZIMqtWrTLGGDN16lQjyXzyyScV7mvM6e/U9ddfbyIiIkzt2rVNmzZtzLRp0xzarF271nTp0sXUrl3bhIeHmxtuuMFs3bq11HNlZ2ebXr16mXr16pk6deqYHj16mMzMzDKPzbp168ywYcNMgwYNTEREhP33c+fONc2bNzehoaGmU6dO5vPPPy/1vSv5Tp35tzF48GBTp04d8/PPP5sbb7zR1KlTx0RFRZlHH320VA/Pc889Z5KTk01kZKQJDQ01HTp0MG+99ZZDm7K+W2d+Hj///LMZMmSIadiwoQkODjbx8fHm1VdfLXVMXn75ZRMfH2//f0ZiYqL5xz/+Ue7nAf9CuIFPuu6660y9evXMd999V2Hb8ePHG0nmyiuvNM8995x56aWXzMCBA83jjz9ub9O3b19zyy23mOeee87Mnj3b/PnPfzaSzGOPPWZvs2rVKpOQkGCioqLMokWLzKJFi8y7775rcnNzzcSJE40kM3ToUPvvdu7caYw5fYIJDg42ycnJ5oUXXjAvvviiadu2rQkODjb/+c9/7M9fEm7i4+PNjTfeaGbNmmVmzpxZ7vsqOdm0adPGdO3a1bz88stm+PDhJjAw0HTr1s3YbDZ727NPMseOHTOXX365qVmzphk5cqR5+eWXTdeuXY0k+wnyyJEjZvbs2UaSuemmm+zv65tvvim3ptzcXBMdHW3q1atnxowZY6ZOnWratWtnAgMDzbJly4wxp8PPokWLjCTTs2dP+/NWxGazmauuuspERUWZAQMGmKCgIJOdnV3hfsYY89Zbb5l27dqZcePGmXnz5pknnnjC1K9f3zRp0sQcPXq01DFt37696dGjh5k+fbp59NFHTVBQkLnlllscnnPs2LFGkundu7eZMWOGueuuu0yjRo1MVFSUU+Hm5MmTpl27dqZFixZmx44dpm7duubWW2916v2sWrXKBAcHmyZNmpiMjAwze/Zs89BDD5mUlBR7m9WrV5saNWqYli1bmmeffdZMmDDBREVFmfr16zuEui1btpg6deqY2NhYM2nSJDNlyhTTrFkzExISYr766qtSxyY+Pt50797dTJ8+3UyZMsUYY8wrr7xi/xt7+eWXzSOPPGIiIiJM8+bNnQo3oaGhplWrVuauu+4ys2fPNn/605+MJDNr1iyH933hhReaBx54wMyYMcNMnTrVdO7c2UhyCM+LFi0yISEhpmvXrvbv1pdffmmMOf39vPDCC01cXJyZOHGimT17trnhhhuMJPPiiy/an6MkSPfr18/MnTvXvPTSS+buu+82Dz30kFOfD3wf4QY+adWqVSYoKMgEBQWZ5ORk89e//tWsXLnSFBUVObTbsWOHCQwMNDfddFOpf+GfefI/duxYqde47777TO3atc3x48ft21y95sZms5lLLrnEpKamlnq9Zs2amZ49e9q3lYSbAQMGOHUMSk42iYmJDu+7pEfg/ffft287O9xMmzbNSDJvvPGGfVtRUZFJTk42devWNQUFBcYY16+5eeSRR4wk8+9//9u+rbCw0DRr1sw0bdrU4TOQZIYPH+7U85bYsmWLqVmzppFkHnnkEaf3K+vzzczMNJLM3//+d/u2kmOakpLi8HmNHDnSBAUFmd9//90YY8z+/ftNcHCw6dOnj0O7J554olRPwbmUXGMTGRlZ5jU4ZTl16pRp1qyZadKkifntt98cfndmLQkJCaZhw4bm0KFD9m3ffPONCQwMNIMGDbJv69u3rwkODraHcWOM+fXXX029evVMt27d7NtKjk2XLl0celSKiopMw4YNTUJCgjlx4oR9e0lAcCbcSDITJ050eC/t27c3iYmJDtvO/hyLiopM69atTY8ePRy2l3fNzd13321iY2PNwYMHHbbfeuutJjw83P78N954o2nVqlWp/WEdzJaCT+rZs6cyMzN1ww036JtvvtGzzz6r1NRUNW7cWB988IG93XvvvSebzaZx48YpMNDx6xwQEGD/71q1atn/u7CwUAcPHlTX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AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAlkK4AQAAluIT4WbmzJlq2rSpQkNDlZSUpKysrHLbzp8/X127dlX9+vVVv359paSknLM9AACoXrwebpYuXar09HRlZGQoOztb7dq1U2pqqvbv319m+3Xr1mnAgAH67LPPlJmZqbi4OF133XX65ZdfPFw5AADwRQHGGOPNApKSktSpUyfNmDFDkmSz2RQXF6cRI0Zo1KhRFe5fXFys+vXra8aMGRo0aFCF7QsKChQeHq78/HyFhYWdd/0AAMD9XDl/e7XnpqioSBs3blRKSop9W2BgoFJSUpSZmenUcxw7dkwnT55UZGSku8oEAAB+pIY3X/zgwYMqLi5WdHS0w/bo6Ght27bNqed4/PHH1ahRI4eAdKYTJ07oxIkT9p8LCgoqXzAAAPB5Xr/m5nxMmTJFS5Ys0bvvvqvQ0NAy20yePFnh4eH2R1xcnIerBAAAnuTVcBMVFaWgoCDl5eU5bM/Ly1NMTMw5933++ec1ZcoUrVq1Sm3bti233ejRo5Wfn29/7N27t0pqBwAAvsmr4SY4OFiJiYlau3atfZvNZtPatWuVnJxc7n7PPvusJk2apBUrVqhjx47nfI2QkBCFhYU5PAAAgHV59ZobSUpPT9fgwYPVsWNHde7cWdOmTdPRo0c1ZMgQSdKgQYPUuHFjTZ48WZL0t7/9TePGjdPixYvVtGlT5ebmSpLq1q2runXreu19AAAA3+D1cNO/f38dOHBA48aNU25urhISErRixQr7RcZ79uxRYOD/Ophmz56toqIi9evXz+F5MjIyNH78eE+WDgAAfJDX17nxNNa5AQDA//jNOjcAAABVjXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAshXADAAAs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ixYtTGhoqImLizMPPPCA+e233zxfuB/47LPPyvx/ackxHTx4sOnevXupfRISEkxwcLBp3ry5WbBggcfrPpcAY+ijAwAA1sE1NwAAwFIINwAAwFIINwAAwFIINwAAwFIINwAAwFIINwAAwFIINwAAwFIINwAAwFIINwD8WnFxsa688krdfPPNDtvz8/MVFxenMWPGeKkyAN7CCsUA/N5///tfJSQkaP78+brtttskSYMGDdI333yjDRs2cDNKoJoh3ACwhJdfflnjx4/X999/r6ysLP35z3/Whg0b1K5dO2+XBsDDCDcALMEYox49eigoKEjfffedRowYobFjx3q7LABeQLgBYBnbtm3T5ZdfrjZt2ig7O1s1atTwdkkAvIALigFYxmuvvabatWsrJydHP//8s7fLAeAl9NwAsIQvv/xS3bt316pVq/TUU09JktasWaOAgAAvVwbA0+i5AeD3jh07pjvvvFPDhg3TNddco1dffVVZWVmaM2eOt0sD4AX03ADwew8//LCWL1+ub775RrVr15YkzZ07V4899pi+++47NW3a1LsFAvAowg0Av/avf/1L1157rdatW6cuXbo4/C41NVWnTp1ieAqoZgg3AADAUrjmBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWArhBgAAWMr/A2BwwcDn0UjZAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot('data/processed/spoter_train.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[18 19 20 21 22 23 24 25 26 27 28 29 30]\n", + " frame row_id type landmark_index x y z\n", + "3801 25 25-face-0 face 0 0.498506 0.494856 -0.018860\n", + "3802 25 25-face-1 face 1 0.506121 0.470006 -0.056474\n", + "3803 25 25-face-2 face 2 0.505620 0.475341 -0.025482\n", + "3804 25 25-face-3 face 3 0.497120 0.439673 -0.049148\n", + "3805 25 25-face-4 face 4 0.506552 0.461902 -0.062083\n" + ] + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "customdata": [ + [ + "wrist_left" + ], + [ + "thumbCMC_left" + ], + [ + "thumbMP_left" + ], + [ + "thumbIP_left" + ], + [ + "thumbTip_left" + ], + [ + "indexMCP_left" + ], + [ + "indexPIP_left" + ], + [ + "indexDIP_left" + ], + [ + "indexTip_left" + ], + [ + "middleMCP_left" + ], + [ + "middlePIP_left" + ], + [ + "middleDIP_left" + ], + [ + "middleTip_left" + ], + [ + "ringMCP_left" + ], + [ + "ringPIP_left" + ], + [ + "ringDIP_left" + ], + [ + "ringTip_left" + ], + [ + "littleMCP_left" + ], + [ + "littlePIP_left" + ], + [ + "littleDIP_left" + ], + [ + "littleTip_left" + ] + ], + "hovertemplate": "type=left_hand
x=%{x}
y=%{y}
name=%{customdata[0]}", + "legendgroup": "left_hand", + "marker": { + "color": "#636efa", + "symbol": "circle" + }, + "mode": "markers", + "name": "left_hand", + "orientation": "v", + "showlegend": true, + "type": "scatter", + "x": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ], + "xaxis": "x", + "y": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ], + "yaxis": "y" + }, + { + "customdata": [ + [ + "nose" + ], + [ + "leftEye" + ], + [ + "rightEye" + ], + [ + "leftEar" + ], + [ + "rightEar" + ], + [ + "leftShoulder" + ], + [ + "rightShoulder" + ], + [ + "leftElbow" + ], + [ + "rightElbow" + ], + [ + "leftWrist" + ], + [ + "rightWrist" + ] + ], + "hovertemplate": "type=pose
x=%{x}
y=%{y}
name=%{customdata[0]}", + "legendgroup": "pose", + "marker": { + "color": "#EF553B", + "symbol": "circle" + }, + "mode": "markers", + "name": "pose", + "orientation": "v", + "showlegend": true, + "type": "scatter", + "x": [ + 0.5037816762924194, + 0.544114351272583, + 0.47258099913597107, + 0.6112894415855408, + 0.42202046513557434, + 0.790356457233429, + 0.30560430884361267, + 0.8586887121200562, + 0.052559275180101395, + 0.8901190161705017, + 0.14568309485912323 + ], + "xaxis": "x", + "y": [ + 0.5385957062244415, + 0.5870676934719086, + 0.5835375487804413, + 0.5667203366756439, + 0.5620827376842499, + 0.3601607084274292, + 0.3561617136001587, + 0.10383915901184082, + 0.12815016508102417, + -0.1593785285949707, + 0.4648846983909607 + ], + "yaxis": "y" + }, + { + "customdata": [ + [ + "wrist_right" + ], + [ + "thumbCMC_right" + ], + [ + "thumbMP_right" + ], + [ + "thumbIP_right" + ], + [ + "thumbTip_right" + ], + [ + "indexMCP_right" + ], + [ + "indexPIP_right" + ], + [ + "indexDIP_right" + ], + [ + "indexTip_right" + ], + [ + "middleMCP_right" + ], + [ + "middlePIP_right" + ], + [ + "middleDIP_right" + ], + [ + "middleTip_right" + ], + [ + "ringMCP_right" + ], + [ + "ringPIP_right" + ], + [ + "ringDIP_right" + ], + [ + "ringTip_right" + ], + [ + "littleMCP_right" + ], + [ + "littlePIP_right" + ], + [ + "littleDIP_right" + ], + [ + "littleTip_right" + ] + ], + "hovertemplate": "type=right_hand
x=%{x}
y=%{y}
name=%{customdata[0]}", + "legendgroup": "right_hand", + "marker": { + "color": "#00cc96", + "symbol": "circle" + }, + "mode": "markers", + "name": "right_hand", + "orientation": "v", + "showlegend": true, + "type": "scatter", + "x": [ + 0.1788196861743927, + 0.23843127489089966, + 0.25467628240585327, + 0.27044928073883057, + 0.2871263027191162, + 0.19056828320026398, + 0.2649158835411072, + 0.3157113492488861, + 0.3494824171066284, + 0.18622460961341858, + 0.2733367681503296, + 0.32277774810791016, + 0.3569425642490387, + 0.19574135541915894, + 0.2806362509727478, + 0.3286181688308716, + 0.36066585779190063, + 0.21657982468605042, + 0.2816353738307953, + 0.31945061683654785, + 0.34714818000793457 + ], + "xaxis": "x", + "y": [ + 0.4631972312927246, + 0.49802637100219727, + 0.5355206429958344, + 0.5633883774280548, + 0.5824070870876312, + 0.5867454409599304, + 0.6103077828884125, + 0.6078206300735474, + 0.6010101139545441, + 0.5849654078483582, + 0.6130928993225098, + 0.6087997257709503, + 0.6008164584636688, + 0.5775772035121918, + 0.6042222082614899, + 0.6019154489040375, + 0.5936585664749146, + 0.564491868019104, + 0.5853257775306702, + 0.5884504020214081, + 0.5863424837589264 + ], + "yaxis": "y" + } + ], + "layout": { + "legend": { + "title": { + "text": "type" + }, + "tracegroupgap": 0 + }, + "margin": { + "t": 60 + }, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "xaxis": { + "anchor": "y", + "constrain": "domain", + "domain": [ + 0, + 1 + ], + "range": [ + 0, + 1 + ], + "title": { + "text": "x" + } + }, + "yaxis": { + "anchor": "x", + "domain": [ + 0, + 1 + ], + "range": [ + 0, + 1 + ], + "scaleanchor": "x", + "scaleratio": 1, + "title": { + "text": "y" + } + } + } + }, + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# read parquet file at data/train_landmark_files/2044/635217.parquet\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "df = pd.read_parquet('data/train_landmark_files/37779/254554888.parquet')\n", + "\n", + "# print all unique frame numbers\n", + "print(df['frame'].unique())\n", + "\n", + "# filter where type is pose\n", + "df = df[df['frame'] == 25]\n", + "\n", + "print(df.head())\n", + "\n", + "mapping = {\n", + " 'pose_0': 'nose',\n", + " 'pose_1': 'leftEye',\n", + " 'pose_4': 'rightEye',\n", + " 'pose_7': 'leftEar',\n", + " 'pose_8': 'rightEar',\n", + " 'pose_11': 'leftShoulder',\n", + " 'pose_12': 'rightShoulder',\n", + " 'pose_13': 'leftElbow',\n", + " 'pose_14': 'rightElbow',\n", + " 'pose_15': 'leftWrist',\n", + " 'pose_16': 'rightWrist',\n", + "\n", + " 'left_hand_0': 'wrist_left',\n", + " 'left_hand_1': 'thumbCMC_left',\n", + " 'left_hand_2': 'thumbMP_left',\n", + " 'left_hand_3': 'thumbIP_left',\n", + " 'left_hand_4': 'thumbTip_left',\n", + " 'left_hand_5': 'indexMCP_left',\n", + " 'left_hand_6': 'indexPIP_left',\n", + " 'left_hand_7': 'indexDIP_left',\n", + " 'left_hand_8': 'indexTip_left',\n", + " 'left_hand_9': 'middleMCP_left',\n", + " 'left_hand_10': 'middlePIP_left',\n", + " 'left_hand_11': 'middleDIP_left',\n", + " 'left_hand_12': 'middleTip_left',\n", + " 'left_hand_13': 'ringMCP_left',\n", + " 'left_hand_14': 'ringPIP_left',\n", + " 'left_hand_15': 'ringDIP_left',\n", + " 'left_hand_16': 'ringTip_left',\n", + " 'left_hand_17': 'littleMCP_left',\n", + " 'left_hand_18': 'littlePIP_left',\n", + " 'left_hand_19': 'littleDIP_left',\n", + " 'left_hand_20': 'littleTip_left',\n", + "\n", + " 'right_hand_0': 'wrist_right',\n", + " 'right_hand_1': 'thumbCMC_right',\n", + " 'right_hand_2': 'thumbMP_right',\n", + " 'right_hand_3': 'thumbIP_right',\n", + " 'right_hand_4': 'thumbTip_right',\n", + " 'right_hand_5': 'indexMCP_right',\n", + " 'right_hand_6': 'indexPIP_right',\n", + " 'right_hand_7': 'indexDIP_right',\n", + " 'right_hand_8': 'indexTip_right',\n", + " 'right_hand_9': 'middleMCP_right',\n", + " 'right_hand_10': 'middlePIP_right',\n", + " 'right_hand_11': 'middleDIP_right',\n", + " 'right_hand_12': 'middleTip_right',\n", + " 'right_hand_13': 'ringMCP_right',\n", + " 'right_hand_14': 'ringPIP_right',\n", + " 'right_hand_15': 'ringDIP_right',\n", + " 'right_hand_16': 'ringTip_right',\n", + " 'right_hand_17': 'littleMCP_right',\n", + " 'right_hand_18': 'littlePIP_right',\n", + " 'right_hand_19': 'littleDIP_right',\n", + " 'right_hand_20': 'littleTip_right',\n", + " }\n", + "\n", + "# scatter plot and when hovering over the point, show the frame number\n", + "import plotly.express as px\n", + "\n", + "# combine type and landmark index\n", + "df['landmark_id'] = df['type'] + '_' + df['landmark_index'].astype(str)\n", + "# only keep rows where landmark_id is in the mapping\n", + "df = df[df['landmark_id'].isin(mapping.keys())]\n", + "df['name'] = df['landmark_id'].apply(lambda x: mapping[x])\n", + "\n", + "# flip vertically\n", + "df['y'] = 1 - df['y']\n", + "\n", + "# keep aspect ratio\n", + "\n", + "# scatter with px and set color of points based on the type\n", + "fig = px.scatter(df, x='x', y='y', color='type', hover_data=['name'])\n", + "\n", + "fig.update_xaxes(range=[0, 1], constrain='domain')\n", + "fig.update_yaxes(scaleanchor='x', scaleratio=1, range=[0, 1])\n", + "\n", + "# show the plot\n", + "fig.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# from training dataset\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +}