Summary of the analysis implemented in analyze_trajectories.py and related outputs in trajectory_analysis/.
The script reads trajectory_filtered.csv from each trial under a predictions directory (e.g. predictions3D/), aggregates stats, and produces CSVs and plots. Trial types (left/right angles, vertical vs angled) come from trial_types.csv (generated by cbot_climb_log/export_trial_types_for_trajectories.py).
-
Rule: For each trial, take the highest-elevation point (peak z). At that point, look at the corresponding x:
- x > -10 → mouse chose left to climb
- x ≤ -10 → mouse chose right to climb
-
Output:
trajectory_analysis/mouse_choice.csv- Columns:
trial_id,peak_x,peak_y,peak_z,side_pick - If
trial_types.csvexists: merged withleft_angle_deg,right_angle_deg,trial_type
- Columns:
- Plot:
trajectory_analysis/side_pick_angle_vs_angle.png - Content:
- Left angle (x-axis) vs Right angle (y-axis), with 360° labelled as “vertical”.
- Heatmap: For each 45°×45° bin, % choose left (0–100%). Blue = more left, red = more right; empty bins stay white.
- Lines: Vertical/horizontal lines at 0°, 90°, 180°, 270°, and 360° so angle bins are clear.
- Data: Uses
mouse_choice.csv(and thustrial_types.csvfor angles). - Trials where both left and right are vertical are not excluded from this plot (it’s a global heatmap over all trial types).
- Vertical on left:
left_angle_deg == 360andright_angle_deg != 360(vertical on left, other side angled). - Vertical on right:
right_angle_deg == 360andleft_angle_deg != 360(vertical on right, other side angled). - Excluded: Trials where both left and right are vertical (360 vs 360) are excluded from the “vertical on left” and “vertical on right” subsets.
These filters are used for the trajectory plots and flow fields below.
- All trials:
trajectory_analysis/all_trials_xy.png— all trials, trajectories colored by elevation z. - Vertical on left:
trajectory_analysis/trajectories_xy_vertical_on_left.png— only trials with vertical on left (both-vertical excluded). - Vertical on right:
trajectory_analysis/trajectories_xy_vertical_on_right.png— only trials with vertical on right (both-vertical excluded).
Same style: top-down x–y, segments colored by z (viridis), same masking (z ≤ 150 and y/z cap for inconsistent region).
- All trials:
trajectory_analysis/flow_field_low_to_high.png— two panels (elevation + flow direction; flow speed + flow direction) over all trials. - Vertical on left:
trajectory_analysis/flow_field_low_to_high_vertical_on_left.png— same two panels, only trials with vertical on left (both-vertical excluded). - Vertical on right:
trajectory_analysis/flow_field_low_to_high_vertical_on_right.png— same two panels, only trials with vertical on right (both-vertical excluded).
Logic: 40×40 spatial grid, dz-weighted flow and speed, turbo colormap, black arrows. See docs/FLOW_FIELD_PLOTS.md for details.
When the predictions root contains session folders (e.g. rory_2025_12_23_16_57_09, wilfred_2026_01_08_16_05_24), the script:
-
Per-trial columns: Each row in
trajectory_stats_summary.csv(and peak/path-to-peak CSVs) includesanimal,session_folder,session_date,phase, andsession_rank.- phase is derived from session order: early (first third of sessions), mid (middle third), late (last third), or single when the animal has only one session.
- session_rank is 1-based session index (1 = first session for that animal).
-
Phase summary:
trajectory_analysis/phase_summary.csv— one row per (animal, phase) withn_trials,mean_path_length_3d,std_path_length_3d,mean_duration_frames,mean_n_segments, etc., so you can compare trajectory metrics early vs mid vs late sessions. -
Plots:
path_length_by_phase.png— boxplot of path length by phase (early/mid/late) per animal.path_length_vs_session_rank.png— path length vs session order (1, 2, 3, …) per animal with optional linear trend.trajectories_xy_by_animal_phase.png— all trials, 2×3 grid (rory/wilfred × early/mid/late).trajectories_xy_vertical_on_left_by_animal_phase.png— vertical-on-left trials only, same 2×3 grid.trajectories_xy_vertical_on_right_by_animal_phase.png— vertical-on-right trials only, same 2×3 grid.
Filter by animal: Use --animal to restrict analysis to one or more animals (e.g. all trials for one animal):
python analyze_trajectories.py /path/to/predictions3D -o trajectory_analysis --animal rory
python analyze_trajectories.py /path/to/predictions3D -o trajectory_analysis --animal rory wilfredtrajectory_stats_summary.csv— path length, duration, n_points, n_segments, animal, session_folder, phase, session_rank per trial.phase_summary.csv— early/mid/late summary per animal (when using nested session layout).peak_elevation_points.csv— (x, y, z, frame, animal, session_folder, phase) of peak z per trial.path_to_peak_summary.csv— frames/seconds/path length to peak per trial (with animal, phase).trial_types.csv— produced bycbot_climb_log/export_trial_types_for_trajectories.py; used for angles and trial-type subsets.- Various other plots (example trajectories, elevation vs frame, boxplots, path length by phase, path length vs session rank, etc.).
# From repo root; trial_types.csv should already exist in trajectory_analysis/
python analyze_trajectories.py predictions3D -o trajectory_analysis
# JARVIS predictions root (multiple sessions, rory and wilfred)
python analyze_trajectories.py /home/user/src/JARVIS-HybridNet/projects/mouseClimb4/predictions/predictions3D -o trajectory_analysis
# One animal only (all sessions and trials for that animal)
python analyze_trajectories.py /path/to/predictions3D -o trajectory_analysis --animal roryFor the trial-type–specific plots and flow fields (including vertical on left/right by animal × phase), trajectory_analysis/trial_types.csv must exist. To build it from rory and wilfred logs:
cd cbot_climb_log
python export_trial_types_for_trajectories.py \
--predictions-dir /path/to/predictions3D \
--animals rory wilfred \
-o ../trajectory_analysis/trial_types.csv \
--logs-dir logsThe script matches each prediction session folder (e.g. rory_2025_12_23_16_57_09) to the closest same-day log session under logs/rory/ and logs/wilfred/, and reads left_angle_deg / right_angle_deg from robot_manager.log. Optionally use trial_frames.csv (from extract_trial_frames.py for rory/wilfred) for exact frame-range matching.