An offline trajectory smoothing, resampling, and evaluation toolkit
Raw teleoperation → Clean trajectories → Metrics & offline task checking
Overview • Quick Start • Core Tools • Data Formats • Pipelines • File Structure • Notes
AxisDataProcessing provides an end‑to‑end offline trajectory processing pipeline for Axis teleoperation data (e.g. from tasks defined in AxisTaskGen and executed in AxisWebInfra).
It is designed to:
- Ingest raw trajectories from JSON or CSV (single or multiple trajectories per file).
- Smooth and resample joint trajectories using Savitzky–Golay + cubic spline, with Franka gripper clamping and optional 3D end‑effector visualization.
- Compute quantitative metrics comparing before/after trajectories (smoothness, joint deviation, end‑effector deviation, frame‑removal ratio).
- Run offline task checkers in pure Python, mirroring the front‑end
checker_configlogic. - Support batch processing via a simple
benchmark.shscript.
Typical use cases:
- Cleaning large batches of user teleoperation data before training.
- Comparing different smoothing/processing configurations.
- Verifying whether an offline trajectory satisfies a task’s success checker.
- Python: 3.10+
- Core Python deps:
numpyscipymatplotlib
- Optional (for end‑effector 3D plots and EE metrics):
roboticstoolbox-python(imported asroboticstoolbox)
Install minimal dependencies:
pip install numpy scipy matplotlib
# Optional: required for end-effector FK plots & EE metrics
pip install roboticstoolbox-pythonIf
roboticstoolboxis not installed, all tools still work; they will automatically fall back to joint‑space visualizations only.
Purpose
- Load one or many trajectories from JSON or CSV.
- Optionally merge static frames, smooth, then cubic‑spline resample to a uniform
dt. - Clamp Franka finger joints to a valid range.
- Optionally generate comparison plots:
- 3D end‑effector trajectory (requires
roboticstoolbox), or - Fallback joint‑space time series.
- 3D end‑effector trajectory (requires
Key Features
- Supports:
- Single trajectory: root object with
trajectory_data.steps. - Multiple trajectories:
{"trajectories": [...]}or root JSON array. - CSV with:
time/simulation_time/t/timestamp+ joint columns.- Optional
trajectory_id/traj_idfor multi‑trajectory tables.
- Large CSV where each row stores one trajectory in a
trajectory_dataJSON string (streaming mode).
- Single trajectory: root object with
- Modes:
resample: deduplicate timestamps → cubic spline resample.smooth_resample: merge static frames → Savitzky–Golay smoothing → cubic spline resample.
- Automatically clamps finger joints with names containing
"finger"to [0.0, 0.04].
CLI Usage
python smooth_resampled_traj.py --input <input-path> --output <output-path> [options]Required arguments
| Argument | Short | Description |
|---|---|---|
--input |
-i |
Input file or directory (.json / .csv) |
--output |
-o |
Output file or directory (.json / .csv) |
Common options
| Argument | Short | Default | Description |
|---|---|---|---|
--mode |
-m |
smooth_resample |
resample (only resample) or smooth_resample (merge + smooth + resample) |
--dt |
0.015 |
Target time step (seconds) after resampling | |
--merge-threshold |
1e-5 |
Threshold for detecting “static” frames (joint change < threshold) | |
--smooth-window |
15 |
Savitzky–Golay window length (odd; only for smooth_resample) |
|
--smooth-poly |
3 |
Savitzky–Golay polynomial order | |
--format |
-f |
auto |
auto (by extension), json, or csv |
--no-visualize |
off | Do not generate any plots | |
--visualize-path |
derived from output | Custom path for visualization image | |
--show |
off | Show interactive matplotlib window (blocks until closed) |
Examples
- Resample only (dt = 0.015)
python smooth_resampled_traj.py \
-i trajectory.json \
-o trajectory_resampled.json \
--mode resample \
--dt 0.015- Smooth + resample (dt = 0.015)
python smooth_resampled_traj.py \
-i trajectory.json \
-o trajectory_smoothed_resampled.json \
--mode smooth_resample \
--dt 0.015- CSV → JSON (no visualization)
python smooth_resampled_traj.py \
-i trajectory_sample.csv \
-o out.json \
--mode resample \
--dt 0.02 \
--no-visualize- Tuning thresholds and smoothing window
python smooth_resampled_traj.py \
-i trajectory.json \
-o out.json \
--mode smooth_resample \
--dt 0.02 \
--merge-threshold 1e-6 \
--smooth-window 21 \
--smooth-poly 3- JSON → CSV
python smooth_resampled_traj.py \
-i trajectory.json \
-o trajectory_out.csv \
--mode resample \
--dt 0.015- Directory batch processing
If --input is a directory:
- All
*.json/*.csvfiles in that directory are processed. --outputmust be a directory (or will be interpreted as such).- Each input file produces
<stem>_smoothed.jsonor<stem>_smoothed.csv.
python smooth_resampled_traj.py \
-i ./raw_traj \
-o ./smoothed_traj \
--mode smooth_resample \
--dt 0.015Purpose
Compute quantitative metrics for optimized trajectories, comparing before and after versions:
- Smoothness:
- Discrete acceleration / jerk statistics per joint and aggregated.
- Position deviation:
- Joint‑space deviation (before interpolated to after’s time grid).
- Optional end‑effector deviation (Euclidean distance in meters).
- Frame‑removal ratio:
- Fraction of frames removed by the “merge static frames” logic, using the same
merge_thresholdassmooth_resampled_traj.py.
- Fraction of frames removed by the “merge static frames” logic, using the same
This is intended for batch evaluation of smoothing pipelines.
CLI Usage
python -m util.metrics_util \
--before <before-path> \
--after <after-path> \
--output <metrics-output> \
--output-format bothArguments
| Argument | Short | Description |
|---|---|---|
--before |
-b |
Trajectory file or directory (raw / before optimization) |
--after |
-a |
Trajectory file or directory (processed / after optimization) |
--output |
-o |
Output path prefix (extension is determined by --output-format) |
--output-format |
json | csv | both (default json) |
|
--no-ee |
Skip end‑effector deviation metrics (if roboticstoolbox is unavailable, EE is skipped) |
|
--merge-threshold |
Threshold for static‑frame merging (should match smooth_resampled_traj.py) |
|
--test |
Run built‑in tests instead of processing trajectories |
Input modes
-
File–file:
--beforeand--afterare single files:- Each may contain single or multiple trajectories.
- Trajectories are aligned by index (min length of both lists).
-
Directory–directory:
- All
*.json/*.csvin--beforeare paired with--afterby normalized stem:foo.json↔foo.jsonorfoo_smoothed.json.
- Metrics are computed for each pair and for each trajectory in the files.
- All
Outputs
-
JSON (
*.json):{ "num_trajectories": N, "metrics_per_trajectory": [ { "trajectory_index": 0, "source_before": "57-1.json", "source_after": "57-1_smoothed.json", "smoothness_before": { "...": "..." }, "smoothness_after": { "...": "..." }, "position_deviation_joint": { "...": "..." }, "position_deviation_ee": { "...": "..." }, "removed_frames_ratio": 0.42, "meta_before": { "...": "..." } } ] } -
CSV (
*.csv): flattened, one row per trajectory with columns such as:trajectory_indexsmoothness_before_acc_mean_abs,smoothness_after_acc_max_absposition_deviation_joint_max_abs,position_deviation_ee_mean_mremoved_frames_ratiometa_attempt_id,meta_task_id, etc.
Example
python -m util.metrics_util \
-b ./raw_traj \
-a ./smoothed_traj \
-o ./batch_metrics \
--output-format bothThis will produce:
batch_metrics.jsonbatch_metrics.csv
Purpose
Run task success checkers on an offline trajectory JSON, using the same checker_config as the web front‑end.
- Loads
stepsandtrajectory_metadata.task_idfrom trajectory JSON. - Fetches the corresponding
checker_configfrom one of:- A backend API.
- A local directory of task JSONs.
- A single task JSON file.
- Re‑implements all supported checker types in pure Python, operating only on the final
stateof the trajectory (position, orientation, joints, etc.). - Prints whether the trajectory passes the checker.
Trajectory JSON requirements
-
Root object containing:
{ "trajectory_metadata": { "task_id": 57, "goal_achieved": true }, "trajectory_data": { "steps": [ { "simulation_time": 0.025, "state": { "robot_joints": { "...": 0.1 }, "object_positions": { "...": [0.0, 0.0, 0.0] }, "object_orientations": { "...": [1.0, 0.0, 0.0, 0.0] } } } ] } }or directly:
{ "steps": [ "... steps ..." ], "trajectory_metadata": { "task_id": 57, "...": "..." } }
CLI Usage
python validate_offline_trajectory.py trajectory.json [options]Options
| Option | Description |
|---|---|
--api-base |
Backend API base URL, e.g. http://localhost:8000 (used to fetch /tasks/{task_id}) |
--tasks-dir |
Directory with task configs (e.g. task_1.json, task_2.json…) |
--tasks-json |
Aggregated tasks JSON file; used by the advanced version in util/validate_offline_trajectory.py |
--task-config |
Single task JSON file that already contains checker_config |
--task-id |
Explicitly specify task ID (overrides trajectory_metadata.task_id) |
--quiet, -q |
Only print true / false |
Examples
- Load task from backend API:
python validate_offline_trajectory.py \
trajectory.json \
--api-base http://localhost:8000- Load from local tasks directory:
python validate_offline_trajectory.py \
trajectory.json \
--tasks-dir ./backend/task_configs- Use a single task config file:
python validate_offline_trajectory.py \
trajectory.json \
--task-config ./task_57.jsonFor more advanced use cases (including tasks JSON aggregations and richer checker support), see
util/validate_offline_trajectory.py.
Purpose
Collect all .json files under a source directory (recursively), then copy them into a flat output directory with renamed, numbered filenames:
PREFIX-1.json,PREFIX-2.json, …
Useful for building standardized batches (e.g. task57 trajectories) before running smoothing + metrics.
CLI Usage
python extra_json.py \
--source-dir <source-dir> \
--output-dir <output-dir> \
--prefix <number-prefix>Arguments
| Argument | Short | Description |
|---|---|---|
--source-dir |
-s |
Root folder to search for .json files (recursively) |
--output-dir |
-o |
Destination folder (defaults to source-dir if omitted) |
--prefix |
-p |
Numeric prefix for filenames (e.g. 57 → 57-1.json, 57-2.json, …) |
A standalone script for visualizing original vs smoothed trajectories in 3D end‑effector space:
- Loads two JSON trajectories (e.g.
trajectory.jsonandtrajectory_smoothed.json). - Uses
roboticstoolboxPanda DH model for FK. - Renders:
- Original trajectory: red dashed line + points.
- Smoothed trajectory: color‑mapped line and points by time.
- Start & end markers, time colorbar, equalized 3D bounds.
For most use cases, the built‑in plotting inside smooth_resampled_traj.py is sufficient; this script is useful for ad‑hoc comparison or debugging.
Purpose
Run the entire pipeline:
- Extract & renumber JSON trajectories.
- Smooth & resample all trajectories in a folder.
- Compute metrics comparing before / after.
Script
./benchmark.shEnvironment variables / inline config
Inside benchmark.sh:
SOURCE_DIR: source directory containing nested trajectory JSONs.OUTPUT_DIR: directory to store extracted + processed data (defaults toSOURCE_DIR).PREFIX: numeric filename prefix for extracted JSONs (e.g.57).
Steps executed:
-
Extract JSON
python extra_json.py \ --source-dir "$SOURCE_DIR" \ --output-dir "$OUTPUT_DIR" \ --prefix "$PREFIX"
-
Smooth trajectories
python smooth_resampled_traj.py \ -i "$TASK_DIR" \ -o "$TASK_DIR/cleaned_data"
-
Compute metrics
python -m util.metrics_util \ -b "$TASK_DIR" \ -a "$TASK_DIR/cleaned_data" \ -o "$TASK_DIR/batch_metrics" \ --output-format both
Results:
- Cleaned trajectories under
cleaned_data/. - Metrics at:
batch_metrics.jsonbatch_metrics.csv
Single trajectory
Root object with trajectory_data.steps:
{
"attempt_id": 26412,
"trajectory_metadata": {
"task_id": 57,
"goal_achieved": true
},
"trajectory_data": {
"steps": [
{
"simulation_time": 0.025,
"state": {
"robot_joints": {
"franka/panda_joint1": 0.1,
"...": 0.2
},
"object_positions": { "...": [0.0, 0.0, 0.0] },
"object_orientations": { "...": [1.0, 0.0, 0.0, 0.0] },
"robot_velocities": { "...": [0.0, 0.0, 0.0] }
}
}
]
}
}Multiple trajectories
Any of the following are accepted:
-
Root has
trajectories:{ "trajectories": [ { "trajectory_data": { "steps": [ "... steps ..." ] }, "...": "..." }, { "trajectory_data": { "steps": [ "... steps ..." ] }, "...": "..." } ] } -
Root is an array:
[ { "trajectory_data": { "steps": [ "... steps ..." ] }, "...": "..." }, { "trajectory_data": { "steps": [ "... steps ..." ] }, "...": "..." } ]
smooth_resampled_traj.py and util/metrics_util.py preserve as much metadata as possible (attempt_id, task_id, etc.) when writing outputs.
Standard format (single or multi‑trajectory)
- Header:
- One time column:
time,simulation_time,t, ortimestamp. - One or more joint columns (column names are joint names).
- Optional
trajectory_idortraj_idcolumn to group multiple trajectories.
- One time column:
Example (single trajectory):
time,franka/panda_joint1,franka/panda_joint2,...,franka/panda_finger_joint1,franka/panda_finger_joint2
0.025,0.0,-0.785,...,0.04,0.04
0.100,0.0,-0.784,...,0.04,0.04Example (multiple trajectories):
trajectory_id,time,franka/panda_joint1,franka/panda_joint2
0,0.000,0.0,-0.785
0,0.020,0.0,-0.784
1,0.000,0.1,-0.780
1,0.020,0.1,-0.779“Task CSV” format (one trajectory per row)
- Column
trajectory_datastores a JSON string:
id,task_id,trajectory_data
1,57,"{\"steps\": [...]}"""This format is handled streamingly by smooth_resampled_traj.py and util/metrics_util.py to support large files.
-
Extract all trajectories for a task
SOURCE_DIR=/path/to/raw/task57 OUTPUT_DIR=/path/to/processed/task57 PREFIX=57 python extra_json.py \ --source-dir "$SOURCE_DIR" \ --output-dir "$OUTPUT_DIR" \ --prefix "$PREFIX"
-
Smooth & resample
python smooth_resampled_traj.py \ -i "$OUTPUT_DIR" \ -o "$OUTPUT_DIR/cleaned_data" \ --mode smooth_resample \ --dt 0.015
-
Compute metrics
python -m util.metrics_util \ -b "$OUTPUT_DIR" \ -a "$OUTPUT_DIR/cleaned_data" \ -o "$OUTPUT_DIR/batch_metrics" \ --output-format both
Given a single offline trajectory JSON:
python validate_offline_trajectory.py \
trajectory.json \
--api-base http://localhost:8000
# or:
python validate_offline_trajectory.py \
trajectory.json \
--tasks-dir ./backend/task_configsUse the exit code / printed result to label trajectories as PASS / FAIL.
High‑level layout:
AxisDataProcessing/
├── raw_traj/ # (example) raw trajectories
├── smoothed_traj/ # (example) processed trajectories
├── util/
│ ├── __init__.py
│ ├── metrics_util.py # Metrics: smoothness, deviation, removed frames
│ └── validate_offline_trajectory.py # Extended offline checker utilities
├── extra_json.py # Extract & renumber JSON trajectories
├── smooth_resampled_traj.py # Core smoothing + resampling CLI
├── validate_offline_trajectory.py # Standalone offline checker runner
├── visualize_trajectory.py # Standalone 3D visualization
├── benchmark.sh # One-command batch pipeline
├── tasks_config.json # (optional) example / aggregated task configs
├── batch_metrics.json # (example) metrics output
├── batch_metrics.csv # (example) metrics output
└── README.md
- All scripts are pure Python and run on the offline trajectories only — no MuJoCo or web stack is required.
roboticstoolboxis used for Franka Panda forward kinematics:- End‑effector plots in
smooth_resampled_traj.pyandvisualize_trajectory.py. - End‑effector deviation metrics in
util/metrics_util.py.
- End‑effector plots in
- When
roboticstoolboxis missing:- EE‑related features are automatically disabled.
- Joint‑space smoothing, resampling, metrics, and checkers still function normally.
- Many thresholds (e.g.
merge_threshold, checker tolerances) are intentionally exposed as CLI flags for experimentation and ablation.
This project builds upon the excellent work of the robotics and Python communities:
- NumPy — Core numerical computing library.
- SciPy — Signal processing and spline interpolation.
- matplotlib — 2D/3D visualization and plotting.
- roboticstoolbox-python — Kinematics and dynamics for the Franka Panda arm.
This project is licensed under the Apache License 2.0 — see the LICENSE file for details.
Built for robotic teleoperation data cleaning and analysis in the Axis ecosystem.