{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":59156,"databundleVersionId":13719397,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"This notebook examines the representations of physical scale in the competition dataset [discussed in this discussion post](https://www.kaggle.com/competitions/MABe-mouse-behavior-detection/discussion/608742).","metadata":{}},{"cell_type":"code","source":"from pathlib import Path\ncompetition_path = Path('/kaggle/input/MABe-mouse-behavior-detection/')\n\nimport pandas as pd\ntrain_df = pd.read_csv(competition_path / 'train.csv', index_col='video_id')\n\ntrain_df['arena_width_pix_approx'] = train_df['arena_width_cm'] * train_df['pix_per_cm_approx']\ntrain_df['arena_height_pix_approx'] = train_df['arena_height_cm'] * train_df['pix_per_cm_approx']\ntrain_df['width_frac'] = train_df['arena_width_pix_approx'] / train_df['video_width_pix']\ntrain_df['height_frac'] = train_df['arena_height_pix_approx']/ train_df['video_height_pix']\ntrain_df['area_frac'] = train_df['height_frac'] * train_df['height_frac']\n\nvoi = train_df.loc[[21954203, 1127527475, 2030735934, 9603]]\nvoi['label'] = voi['lab_id'] + '<br>' + voi.index.astype(str)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-22T00:55:22.365777Z","iopub.execute_input":"2025-09-22T00:55:22.366026Z","iopub.status.idle":"2025-09-22T00:55:22.895089Z","shell.execute_reply.started":"2025-09-22T00:55:22.366004Z","shell.execute_reply":"2025-09-22T00:55:22.893999Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import plotly.express as px\n\nfrac_range = (0.32, 3.125)\nfig = px.scatter(train_df, x='width_frac', y='height_frac', opacity=0.5, log_x=True, log_y=True, range_x=frac_range, range_y=frac_range)\nfig.update_traces(marker=dict(size=6))\nfig.add_hline(y=1, line_color='gray', opacity=0.3)\nfig.add_vline(x=1, line_color='gray', opacity=0.3)\nfig.add_scatter(\n    mode='markers+text', x=voi['width_frac'], y=voi['height_frac'], text=voi['label'],\n    marker=dict(size=12, symbol='circle-open'), textposition='bottom right', showlegend=False\n)\nfig.update_layout(\n    width=600, height=600,\n    title=dict(text='Approx. arena pixel dimensions<br>as fraction of frame', x=0.5, xanchor='center'),\n    xaxis=dict(title='Approx. arena pixel width as fraction of frame'),\n    yaxis=dict(title='Approx. arena pixel height as fraction of frame', scaleanchor='x', scaleratio=1)\n)\nfig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T00:55:22.896039Z","iopub.execute_input":"2025-09-22T00:55:22.896284Z","iopub.status.idle":"2025-09-22T00:55:23.686373Z","shell.execute_reply.started":"2025-09-22T00:55:22.896264Z","shell.execute_reply":"2025-09-22T00:55:23.685363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\n\nstart = np.log10(train_df['area_frac'].min())\nstop = np.log10(train_df['area_frac'].max())\ncounts, bins = np.histogram(train_df['area_frac'], bins=np.logspace(start, stop, 20))\n\nfig, ax = plt.subplots(figsize=(8,3))\n\n# 3. Plot the histogram, passing the data and pre-defined bins\nax.hist(train_df['area_frac'], bins=bins)\n\nxticks = [.2, .5, 1, 2]\nyticks = [1, 10, 100, 1000]\nax.set(\n    xscale='log', yscale='log', xlim=[.1, 5], ylim=[.8,1e4], xticks=xticks, xticklabels=xticks, yticks=yticks, yticklabels=yticks,\n    xlabel='Arena area fraction of frame', ylabel='# of videos', title='Distribution of arena area fraction of frame')\nax.grid(True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T23:04:11.227744Z","iopub.execute_input":"2025-09-21T23:04:11.228046Z","iopub.status.idle":"2025-09-21T23:04:11.713649Z","shell.execute_reply.started":"2025-09-21T23:04:11.228022Z","shell.execute_reply":"2025-09-21T23:04:11.712791Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for video_id, row in voi.iterrows():\n    tracking_df = pd.read_parquet(competition_path / 'train_tracking' / row['lab_id'] / f'{video_id}.parquet', columns=['x','y'])\n    tracking_df['title'] = 'Tracked coordinates'\n    \n    symmetric_extent = np.array((-0.5, 0.5))\n    frame_x = np.array((0.0, row['video_width_pix']))\n    frame_y = np.array((0.0, row['video_height_pix']))\n    arena_x = frame_x.mean() + row['arena_width_pix_approx'] * symmetric_extent\n    arena_y = frame_y.mean() + row['arena_height_pix_approx'] * symmetric_extent\n    all_x = np.concatenate((frame_x,arena_x))\n    all_y = np.concatenate((frame_y,arena_y))\n    \n    padding = 20\n    range_x = (all_x.min()-padding, all_x.max()+padding)\n    range_y = (all_y.min()-padding, all_y.max()+padding)\n    \n    fig = px.scatter(tracking_df, x='x', y='y', color='title', title=f\"{row['lab_id']}/{video_id}\", opacity=0.5, range_x=range_x, range_y=range_y)\n    fig.add_shape(\n        type='rect', showlegend=True, name='Frame',\n        x0=frame_x[0], x1=frame_x[1],\n        y0=frame_y[0], y1=frame_y[1],\n        line=dict(color='green', width=2)\n    )\n    fig.add_shape(\n        type='rect', showlegend=True, name='Arena (\"approx\", centered)',\n        x0=arena_x[0], x1=arena_x[1],\n        y0=arena_y[0], y1=arena_y[1],\n        line=dict(color='red', width=2)\n    )\n    fig.update_traces(marker=dict(size=1))\n    axes_width = np.diff(range_x)[0]\n    axes_height = np.diff(range_y)[0]\n    scale_factor = min(1200.0/axes_width, 900.0/axes_height)\n    fig.update_layout(\n        width=min(axes_width * scale_factor,950), height=axes_height * scale_factor,\n        xaxis_title='x (pix)', yaxis_title='y (pix)', yaxis_scaleanchor='x', yaxis_scaleratio=1,\n        legend=dict(orientation=\"h\", yanchor=\"bottom\", y=1.02, xanchor=\"right\", x=1, title=None)\n    )\n    fig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T23:10:35.317068Z","iopub.execute_input":"2025-09-21T23:10:35.317399Z","iopub.status.idle":"2025-09-21T23:10:35.870410Z","shell.execute_reply.started":"2025-09-21T23:10:35.317374Z","shell.execute_reply":"2025-09-21T23:10:35.869512Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MABe22_df = train_df[train_df['lab_id'].str.contains('MABe22')]\ndisplay(MABe22_df.reset_index()[['video_width_pix','video_height_pix']].drop_duplicates())\nmin_coords = np.array([np.inf, np.inf])\nmax_coords = np.array([-np.inf, -np.inf])\nfor video_id, row in MABe22_df.iterrows():\n    tracking_df = pd.read_parquet(competition_path / 'train_tracking' / row['lab_id'] / f'{video_id}.parquet', columns=['x','y'])\n    min_coords = np.minimum(min_coords,tracking_df.min())\n    max_coords = np.maximum(max_coords,tracking_df.max())\nprint(min_coords)\nprint(max_coords)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T00:03:12.647355Z","iopub.execute_input":"2025-09-22T00:03:12.647761Z","iopub.status.idle":"2025-09-22T00:04:56.720825Z","shell.execute_reply.started":"2025-09-22T00:03:12.647730Z","shell.execute_reply":"2025-09-22T00:04:56.719786Z"}},"outputs":[],"execution_count":null}]}