diff --git a/src/dynamic_foraging_processing/qc/processed/plots.py b/src/dynamic_foraging_processing/qc/processed/plots.py index d04c941..f655485 100644 --- a/src/dynamic_foraging_processing/qc/processed/plots.py +++ b/src/dynamic_foraging_processing/qc/processed/plots.py @@ -122,7 +122,7 @@ def _add_lickspout_position_plot( if position is None or len(position) == 0: continue values = np.asarray(position, dtype=float) - ax.plot(values - values[0], color, label=label) + ax.plot(values, color, label=label) plotted = True if plotted: ax.legend() @@ -303,6 +303,14 @@ def plot_side_bias( ) _add_reward_probabilities(ax[3], reward_probability_left, reward_probability_right) + # Align the x-axis across every panel so trials line up vertically. The + # panels are all indexed by trial, but some auto-scale (adding margins) while + # others set [0, N]; pin them all to a common [0, n_trials]. + n_trials = max(len(np.asarray(side_bias)), len(np.asarray(animal_response))) + if n_trials: + for axis in ax: + axis.set_xlim([0, n_trials]) + fig.savefig(Path(results_folder) / SIDE_BIAS_PLOT, dpi=300, bbox_inches="tight") plt.close(fig) return SIDE_BIAS_PLOT diff --git a/src/dynamic_foraging_processing/qc/processed/results.py b/src/dynamic_foraging_processing/qc/processed/results.py index 762890f..a352d3d 100644 --- a/src/dynamic_foraging_processing/qc/processed/results.py +++ b/src/dynamic_foraging_processing/qc/processed/results.py @@ -22,15 +22,15 @@ from dynamic_foraging_processing.qc.processed.plots import plot_lick_intervals, plot_side_bias # Logical input -> trials-table column name. Centralized so the mapping is easy -# to correct against the trial-table builder; ``side_bias`` and the -# ``lickspout_*`` arrays are not yet pinned down in trials_table_mapping.md. +# to correct against the trial-table builder. The lickspout positions are +# emitted by ``_TrialTableBuilder._lickspout_columns`` as ``lickspout_position_*``. _COLUMNS = { "animal_response": "animal_response", "side_bias": "side_bias", - "lickspout_x": "lickspout_x", - "lickspout_y1": "lickspout_y1", - "lickspout_y2": "lickspout_y2", - "lickspout_z": "lickspout_z", + "lickspout_x": "lickspout_position_x", + "lickspout_y1": "lickspout_position_y1", + "lickspout_y2": "lickspout_position_y2", + "lickspout_z": "lickspout_position_z", "rewarded_left": "rewarded_historyL", "rewarded_right": "rewarded_historyR", "reward_probability_left": "reward_probabilityL", diff --git a/tests/test_qc/test_builder.py b/tests/test_qc/test_builder.py index 69507e8..d560de8 100644 --- a/tests/test_qc/test_builder.py +++ b/tests/test_qc/test_builder.py @@ -48,6 +48,27 @@ def test_behavior_qc_results_writes_plots(tmp_path): assert os.path.exists(tmp_path / "lick_intervals.png") +def test_column_resolves_lickspout_position_names(): + """``lickspout_*`` inputs map to the trial table's ``lickspout_position_*``. + + Regression guard: the trial-table builder emits ``lickspout_position_x`` etc. + (see ``_TrialTableBuilder._lickspout_columns``), so the mapping must resolve + those names or the lickspout-position plot panel silently comes up empty. + """ + trials = pd.DataFrame( + { + "lickspout_position_x": [1.0, 1.1], + "lickspout_position_y1": [2.0, 2.0], + "lickspout_position_y2": [3.0, 3.1], + "lickspout_position_z": [4.0, 4.0], + } + ) + for key in ("lickspout_x", "lickspout_y1", "lickspout_y2", "lickspout_z"): + column = _results._column(trials, key) + assert column is not None, key + np.testing.assert_array_equal(_results._column(trials, "lickspout_x"), [1.0, 1.1]) + + def test_build_quality_control_defaults(): """Defaults fill in the standard grouping and an empty failure allowlist.""" metrics = to_metrics(