Skip to content

Repository files navigation

pyplotc

pyplotc is a Python tool for visualizing space-separated telemetry/log data generated by C/C++ code. It supports both legacy fprintf structure files and newer std::ostringstream data-writing snippets, and can also read column names from a # header line in the data file itself.

Plotting is available through Gnuplot or Matplotlib.

Features

  • Automatic parsing of C/C++ structure files:
    • fprintf format via fprintf_structure.py
    • std::ostringstream data-writing format via oss_structure.py
  • Optional # header line in data files for explicit column names (for example arm0_j0_set_velocity)
  • Binary CTLG telemetry (.bin) with column schema from file header via telemetry_helper.py
  • Flexible plotting: select columns by index or variable name, split series into multiple graphs with :
  • Supports Gnuplot and Matplotlib

Installation

Install Python dependencies:

pip install -r requirements.txt

Or install packages individually:

pip install py-gnuplot==1.3 matplotlib pandas pycparser

For Gnuplot plotting (--plot_tool=gnuplot), also install the system gnuplot executable:

sudo apt-get install gnuplot

Column Mapping Priority

When resolving column names, plot_data.py uses this order:

  1. --structure_file if provided
  2. Else # header line in the data file
  3. Else error

If a data file contains a # header but you also pass --structure_file, the structure file takes precedence. The header row is still skipped when reading data.

If the header lists more names than the data rows actually contain (max-size header vs runtime-sized rows), pyplotc warns and only plots columns whose index exists in the data.

Quick Start

  1. Add a structure file in data_structure/ (copy the C++ fprintf / oss << data-writing snippet).
  2. Update replacement.json with numeric loop bounds that match your robot (e.g. NUM_ARM, ctx.robot_state.arm_size).
  3. Run plot_data.py with --structure_file, --data_file, and --target.

Usage

Example 1: Legacy fprintf structure

python plot_data.py \
  --structure_file=data_structure/example.txt \
  --data_file=data/example.txt \
  --target=3,17:position_error

In the first graph, 3,17 does not mean “only column 3 and column 17”. Each number looks up that column’s variable name and plots every column with the same name. With example.txt, column 3 is set_velocity and column 17 is read_velocity, so the first graph plots all set_velocity and all read_velocity columns. The second graph (position_error) plots every column with that name.

Example 2: ostringstream data structure

Structure file data_structure/example_oss.txt describes how each data row is written with stream insertion.

python plot_data.py \
  --structure_file=data_structure/example_oss.txt \
  --data_file=data/example.txt \
  --target=set_velocity,position_error

Repeated field names such as set_velocity expand to every matching column (both arms and all joints).

Example 3: Data file with # header only

data/example_header.txt starts with a comment header:

# arm0_j0_set_velocity arm0_j1_set_velocity ... status t1 t2 receiver_communication_time receiver_running_time
1.1000 1.2000 ...

No structure file is required when the header is present:

python plot_data.py \
  --data_file=data/example_header.txt \
  --target=arm0_j0_set_velocity,position_error_0

Use the exact names from the header row when targeting specific joints or array elements.

Example 4: Binary CTLG telemetry (.bin)

Binary telemetry files carry their own column schema in the CTLG header. No structure file is required:

python plot_data.py \
  --data_file=data/telemetry.bin \
  --target=motor_target,joint_velocity \
  --plot_tool=matplotlib

Semantic aliases are derived from column names (for example arm0_j0_motor_target → motor_target). Use --column_mode to plot by direct column index.

Convert .bin or .txt telemetry to CSV with:

python telemetry_helper.py input.bin -o output.csv

Arguments

Argument Description
--structure_file Optional path to a C/C++ structure snippet (fprintf or ostringstream); not used for .bin telemetry
--data_file Path to the data file (space-separated .txt or CTLG .bin)
--data_directory Optional directory prepended to --data_file (resolved as data_directory/data_file)
--replacement_file JSON macro replacements for loops in structure files (default: replacement.json)
--target Comma-separated columns or variable names; use : for separate graphs
--index Optional comma-separated flat indices (e.g. 0,1) applied per same-name variable to limit expanded columns
--column_mode When set, numeric targets refer to direct column indices instead of variable-name lookup
--start_frame Start row index (default: 0)
--end_frame End row index (default: all rows)
--plot_tool gnuplot (default) or matplotlib

Target Parsing Notes

  • Structure expressions like curobo_command_ptr->JointPosCmd[i] map to target name JointPosCmd
  • --target=set_velocity plots every column with that name
  • With a # header file, use explicit names such as arm0_j0_set_velocity

Column indices are 0-based

All column numbers in --target, printed figure_indexs, and legend labels use 0-based indexing (first data column is 0).

Gnuplot reads files with 1-based columns internally, but pyplotc converts automatically. Legend titles still show the 0-based index (for example 3: set_velocity).

Numeric column targets

A number refers to a column index lookup, then expands to all columns sharing that base name:

Target Column lookup Plotted columns (example.txt)
3 column 3 → set_velocity all set_velocity → 0, 1, 2, 3, 4, 5
17 column 17 → read_velocity all read_velocity → 12, 13, 14, 15, 16, 17
3,17 both lookups in one graph all set_velocity + all read_velocity

If the variable at that column index is unique (appears only once), the result is just that one column.

Index filter (--index)

--index is same-name variable based: indices count within each target's expanded list of columns sharing that variable name, not global file column numbers.

Use --index=0,1 to plot only flat positions 0 and 1 of each expanded same-name target. Explicit bracket syntax is not filtered.

For each target, indices refer to the flattened 1D order in which the structure file expands nested loops (outer loop slowest, inner loop fastest). They are not interpreted as “last loop dimension only”.

For set_velocity[i][j] with NUM_ARM=2, JOINTS_PER_ARM=3, the flattened order is:

arm0_j0, arm0_j1, arm0_j2, arm1_j0, arm1_j1, arm1_j2  →  cols 0–5

So --index=0,1 picks flat positions 0 and 1 (arm0_j0, arm0_j1), not “joint 0 and 1 on every arm”. To pick one flattened element, use set_velocity[2] (0-based index 2 → col 2 here).

Example with example.txt:

python plot_data.py \
  --structure_file=data_structure/example.txt \
  --data_file=data/example.txt \
  --target=3,17:position_error,set_velocity[2] \
  --index=0,1
Graph Target Plotted columns
1 3,17 set_velocity flat[0,1] → 0, 1; read_velocity flat[0,1] → 12, 13
2 position_error position_error flat[0,1] → 18, 19
3 set_velocity[2] col 2 only (explicit index unchanged)

Without --index, 3,17 and position_error plot every matching column.

Variable name targets

Examples below use example.txt with NUM_ARM=2, JOINTS_PER_ARM=3 (see replacement.json).

Target Meaning Result
set_velocity all columns with this name 6 curves (cols 0–5)
set_velocity[2] one element, flat 0-based index 2 1 curve (col 2 = arm0_j2)

Bracket indexing is always flat after nested loops are expanded in the structure file (outer loop slow, inner loop fast). There is no set_velocity[arm][joint] syntax — use the flattened index instead.

  • set_velocity → all arms and joints
  • set_velocity[2] → one flattened element (index 2), not all set_velocity
  • With a # header file, use explicit names such as arm1_j2_set_velocity

Structure Files

Structure files in data_structure/ describe how one output line is written in C/C++.

File Format Purpose
example.txt fprintf Simple tutorial example
example_oss.txt ostringstream Same layout as example.txt, stream style

Each output value must be separated by spaces so columns align with the data file.

Replacement File

replacement.json substitutes macros and loop bounds in structure files:

{
    "NUM_JOINTS": 6,
    "robot.arm_size[0]": 2,
    "robot.joint_size[i]": 3
}

Data Files

Data files contain space-separated numeric values. Each row is one sample/frame.

File Description
data/example.txt Sample data for example.txt / example_oss.txt
data/example_header.txt Same data with a # name header row

Example header + data:

# arm0_j0_set_velocity arm0_j1_set_velocity ... receiver_running_time
0.1000 0.2000 0.3000 ... 1000
0.1100 0.2100 0.3100 ... 1001

Project Layout

pyplotc/
├── plot_data.py           # CLI entry point
├── telemetry_helper.py    # CTLG binary telemetry parser / CSV converter
├── fprintf_structure.py   # fprintf parser
├── oss_structure.py       # ostringstream data parser
├── requirements.txt       # Python dependencies
├── replacement.json
├── data_structure/
│   ├── example.txt
│   ├── example_oss.txt
└── data/
    ├── example.txt
    └── example_header.txt

About

Use python to plot c "fprintf" function

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages