Skip to content

Commit 86c7b5d

Browse files
committed
docs(nb04): update text
1 parent c4c2b22 commit 86c7b5d

1 file changed

Lines changed: 130 additions & 10 deletions

File tree

examples/hawks/04_MorphingShapeModes.ipynb

Lines changed: 130 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
},
5555
{
5656
"cell_type": "code",
57-
"execution_count": null,
57+
"execution_count": 80,
5858
"id": "51181a1c",
5959
"metadata": {
6060
"execution": {
@@ -64,8 +64,62 @@
6464
"shell.execute_reply": "2026-03-16T23:47:17.422247Z"
6565
}
6666
},
67-
"outputs": [],
68-
"source": "# --- Setup ---\n%load_ext autoreload\n%autoreload 2\n%matplotlib inline\n%config InlineBackend.figure_format='retina'\n\n# Reviewer default is False (~5x faster); set True to reproduce exact\n# manuscript iteration counts. Plots are unchanged; p-values are coarser.\nPAPER_MODE = False\n\nCONFIG = {\n 'rng_seed': 42,\n 'n_permutations': 500 if PAPER_MODE else 100,\n}\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nplt.rcParams['font.family'] = 'Andale Mono'\nnp.set_printoptions(suppress=True, precision=3)\n\nfrom morphing_birds import (\n Animal3D, plot_plotly, animate_plotly, animate_plotly_compare,\n animate_mode, animate_compare,\n)\n\nfrom kinematic_morphospace import (\n filter_by, run_PCA, run_PCA_birds, apply_sign_conventions,\n get_score_df, get_score_range, get_binned_raw_scores,\n reconstruct, to_bilateral, prepare_unilateral,\n bin_markers_by_distance,\n)\nfrom kinematic_morphospace.pca_core import get_PCA_input\nfrom kinematic_morphospace.null_testing import ensure_rng, validate_frame_alignment\nfrom kinematic_morphospace.plotting import (\n plot_explained, plot_components_grid,\n plot_score_multi_PCs, compare_coeffs_hawks,\n plot_3d_scatter_with_animation,\n save_figure,\n)\n\nrng = ensure_rng(CONFIG['rng_seed'])"
67+
"outputs": [
68+
{
69+
"name": "stdout",
70+
"output_type": "stream",
71+
"text": [
72+
"The autoreload extension is already loaded. To reload it, use:\n",
73+
" %reload_ext autoreload\n"
74+
]
75+
}
76+
],
77+
"source": [
78+
"# --- Setup ---\n",
79+
"%load_ext autoreload\n",
80+
"%autoreload 2\n",
81+
"%matplotlib inline\n",
82+
"%config InlineBackend.figure_format='retina'\n",
83+
"\n",
84+
"# Reviewer default is False (~5x faster); set True to reproduce exact\n",
85+
"# manuscript iteration counts. Plots are unchanged; p-values are coarser.\n",
86+
"PAPER_MODE = False\n",
87+
"\n",
88+
"CONFIG = {\n",
89+
" 'rng_seed': 42,\n",
90+
" 'n_permutations': 500 if PAPER_MODE else 100,\n",
91+
"}\n",
92+
"\n",
93+
"import os\n",
94+
"import numpy as np\n",
95+
"import pandas as pd\n",
96+
"import matplotlib.pyplot as plt\n",
97+
"\n",
98+
"plt.rcParams['font.family'] = 'Andale Mono'\n",
99+
"np.set_printoptions(suppress=True, precision=3)\n",
100+
"\n",
101+
"from morphing_birds import (\n",
102+
" Animal3D, plot_plotly, animate_plotly, animate_plotly_compare,\n",
103+
" animate_mode, animate_compare,\n",
104+
")\n",
105+
"\n",
106+
"from kinematic_morphospace import (\n",
107+
" filter_by, run_PCA, run_PCA_birds, apply_sign_conventions,\n",
108+
" get_score_df, get_score_range, get_binned_raw_scores,\n",
109+
" reconstruct, to_bilateral, prepare_unilateral,\n",
110+
" bin_markers_by_distance,\n",
111+
")\n",
112+
"from kinematic_morphospace.pca_core import get_PCA_input\n",
113+
"from kinematic_morphospace.null_testing import ensure_rng, validate_frame_alignment\n",
114+
"from kinematic_morphospace.plotting import (\n",
115+
" plot_explained, plot_components_grid,\n",
116+
" plot_score_multi_PCs, compare_coeffs_hawks,\n",
117+
" plot_3d_scatter_with_animation,\n",
118+
" save_figure,\n",
119+
")\n",
120+
"\n",
121+
"rng = ensure_rng(CONFIG['rng_seed'])"
122+
]
69123
},
70124
{
71125
"cell_type": "markdown",
@@ -79,7 +133,7 @@
79133
},
80134
{
81135
"cell_type": "code",
82-
"execution_count": 58,
136+
"execution_count": 81,
83137
"id": "e5c90609",
84138
"metadata": {
85139
"execution": {
@@ -112,12 +166,30 @@
112166
"print(f\"Frames: {len(combined_frame_info_df):,}\")"
113167
]
114168
},
169+
{
170+
"cell_type": "markdown",
171+
"id": "052e6c3b",
172+
"metadata": {},
173+
"source": [
174+
"## View Feather Landmarks in 3D\n",
175+
"\n",
176+
"Drag to rotate. "
177+
]
178+
},
115179
{
116180
"cell_type": "code",
117-
"execution_count": null,
181+
"execution_count": 83,
118182
"id": "bf37ff80",
119183
"metadata": {},
120-
"outputs": [],
184+
"outputs": [
185+
{
186+
"name": "stdout",
187+
"output_type": "stream",
188+
"text": [
189+
"Charmander, 9 m, 2020: 9,396 frames from 121 flights\n"
190+
]
191+
}
192+
],
121193
"source": [
122194
"# Plot 3D scatter for a single hawk (Charmander, 9 m, 2020)\n",
123195
"filt = filter_by(combined_frame_info_df, hawkname=\"Charmander\", perchDist=9, year=2020)\n",
@@ -128,7 +200,7 @@
128200
"x = subset[:, :8, 0].flatten()\n",
129201
"y = subset[:, :8, 1].flatten()\n",
130202
"z = subset[:, :8, 2].flatten()\n",
131-
"_ = plot_3d_scatter_with_animation(x, y, z, browser=False)"
203+
"_ = plot_3d_scatter_with_animation(x, y, z, browser=True)"
132204
]
133205
},
134206
{
@@ -138,7 +210,9 @@
138210
"source": [
139211
"### Example Flight (Rotation-Corrected)\n",
140212
"\n",
141-
"Before decomposing the data, it is helpful to see what the rotation-corrected marker data looks like as a flight. The animation below shows the bilateral marker positions for Toothless (9 m, straight, period 2), averaged across flights by binning on horizontal distance to perch."
213+
"Before decomposing the data, it is helpful to see what the rotation-corrected marker data looks like as a flight. The animation below shows the bilateral marker positions for Toothless (9 m, straight, period 2), averaged across flights by binning on horizontal distance to perch.\n",
214+
"\n",
215+
"Press play/pause, drag the scrollbar, or click and drag the plot to rotate. "
142216
]
143217
},
144218
{
@@ -239683,7 +239757,53 @@
239683239757
}
239684239758
},
239685239759
"outputs": [],
239686-
"source": "# Select a flight: Toothless, 9 m, straight, 2020\nflight = dict(hawkname=\"Toothless\", perchDist=\"9m\", turn=\"Straight\", year=2020)\n\n# --- Original bilateral markers, binned by distance (ground truth) ---\nn = len(combined_frame_info_df)\nbilateral_mask = filter_by(scores_df.iloc[:n], **flight)\nbins = scores_df.iloc[:n].loc[bilateral_mask, \"bins\"]\norig = transformed_markers[bilateral_mask, :8, :]\n\norig_df = pd.DataFrame(orig.reshape(-1, 24), index=bins.index)\norig_df[\"bins\"] = bins.values\norig_binned = orig_df.groupby(\"bins\", observed=True).mean()\n\n# --- Bin raw scores using the same distance bins ---\nleft_binned = get_binned_raw_scores(scores, scores_df, **flight, left=True)\nright_binned = get_binned_raw_scores(scores, scores_df, **flight, left=False)\n\n# Align all to the same bin index\ncommon_bins = orig_binned.index.intersection(left_binned.index).intersection(right_binned.index)\nprint(f\"Bins: {len(orig_binned)} original, {len(left_binned)} left, {len(right_binned)} right, {len(common_bins)} common\")\n\norig_bilateral = orig_binned.loc[common_bins].to_numpy().reshape(-1, 8, 3)\nleft_np = left_binned.loc[common_bins].to_numpy()\nright_np = right_binned.loc[common_bins].to_numpy()\n\nmu = mean_shape.reshape(1, -1, 3)\n\n# --- Sanity check: 12-mode reconstruction must match original ---\nall_modes = list(range(12))\nfull_recon = to_bilateral(\n reconstruct(right_np, principal_components, mu, all_modes),\n left=reconstruct(left_np, principal_components, mu, all_modes),\n)\nmax_err = np.abs(full_recon - orig_bilateral).max()\nprint(f\"Max error (12-mode vs original): {max_err:.2e} m\")\nassert max_err < 1e-10, f\"12-mode reconstruction does not match original! Max error: {max_err}\"\n\n# --- 4-mode reconstruction ---\nmodes = [0, 1, 2, 3]\nrecon_bilateral = to_bilateral(\n reconstruct(right_np, principal_components, mu, modes),\n left=reconstruct(left_np, principal_components, mu, modes),\n)\n\nprint(f\"Reconstructed {len(common_bins)} distance bins using {len(modes)} modes\")"
239760+
"source": [
239761+
"# Select a flight: Toothless, 9 m, straight, 2020\n",
239762+
"flight = dict(hawkname=\"Toothless\", perchDist=\"9m\", turn=\"Straight\", year=2020)\n",
239763+
"\n",
239764+
"# --- Original bilateral markers, binned by distance (ground truth) ---\n",
239765+
"n = len(combined_frame_info_df)\n",
239766+
"bilateral_mask = filter_by(scores_df.iloc[:n], **flight)\n",
239767+
"bins = scores_df.iloc[:n].loc[bilateral_mask, \"bins\"]\n",
239768+
"orig = transformed_markers[bilateral_mask, :8, :]\n",
239769+
"\n",
239770+
"orig_df = pd.DataFrame(orig.reshape(-1, 24), index=bins.index)\n",
239771+
"orig_df[\"bins\"] = bins.values\n",
239772+
"orig_binned = orig_df.groupby(\"bins\", observed=True).mean()\n",
239773+
"\n",
239774+
"# --- Bin raw scores using the same distance bins ---\n",
239775+
"left_binned = get_binned_raw_scores(scores, scores_df, **flight, left=True)\n",
239776+
"right_binned = get_binned_raw_scores(scores, scores_df, **flight, left=False)\n",
239777+
"\n",
239778+
"# Align all to the same bin index\n",
239779+
"common_bins = orig_binned.index.intersection(left_binned.index).intersection(right_binned.index)\n",
239780+
"print(f\"Bins: {len(orig_binned)} original, {len(left_binned)} left, {len(right_binned)} right, {len(common_bins)} common\")\n",
239781+
"\n",
239782+
"orig_bilateral = orig_binned.loc[common_bins].to_numpy().reshape(-1, 8, 3)\n",
239783+
"left_np = left_binned.loc[common_bins].to_numpy()\n",
239784+
"right_np = right_binned.loc[common_bins].to_numpy()\n",
239785+
"\n",
239786+
"mu = mean_shape.reshape(1, -1, 3)\n",
239787+
"\n",
239788+
"# --- Sanity check: 12-mode reconstruction must match original ---\n",
239789+
"all_modes = list(range(12))\n",
239790+
"full_recon = to_bilateral(\n",
239791+
" reconstruct(right_np, principal_components, mu, all_modes),\n",
239792+
" left=reconstruct(left_np, principal_components, mu, all_modes),\n",
239793+
")\n",
239794+
"max_err = np.abs(full_recon - orig_bilateral).max()\n",
239795+
"print(f\"Max error (12-mode vs original): {max_err:.2e} m\")\n",
239796+
"assert max_err < 1e-10, f\"12-mode reconstruction does not match original! Max error: {max_err}\"\n",
239797+
"\n",
239798+
"# --- 4-mode reconstruction ---\n",
239799+
"modes = [0, 1, 2, 3]\n",
239800+
"recon_bilateral = to_bilateral(\n",
239801+
" reconstruct(right_np, principal_components, mu, modes),\n",
239802+
" left=reconstruct(left_np, principal_components, mu, modes),\n",
239803+
")\n",
239804+
"\n",
239805+
"print(f\"Reconstructed {len(common_bins)} distance bins using {len(modes)} modes\")"
239806+
]
239687239807
},
239688239808
{
239689239809
"cell_type": "code",
@@ -404837,4 +404957,4 @@
404837404957
},
404838404958
"nbformat": 4,
404839404959
"nbformat_minor": 5
404840-
}
404960+
}

0 commit comments

Comments
 (0)