Chapter 19: Integration Lab — Building a Complete Robot Stack
Lesson 2: Data Streaming and Visualization
This lesson formalizes how sensor and state data flow continuously through a robot stack, how these streams are modeled mathematically as sampled signals, and how they are visualized for monitoring and debugging. We connect sampling theory, latency and throughput analysis, and time-series visualization to practical middleware implementations in Python, C++, Java, and MATLAB/Simulink.
1. Conceptual Overview of Data Streaming in a Robot Stack
In an integrated robot system, each sensor produces a time-indexed sequence of measurements. Conceptually, a continuous-time physical quantity \( x(t) \) (e.g., joint angle, acceleration, range) is sampled, quantized, transported by middleware, logged, and finally visualized. We model a digital data stream as a sequence \( \{x[k]\}_{k\in\mathbb{Z}} \), where the discrete-time index \( k \) is related to physical time by
\[ t_k = k T_s, \quad T_s = \frac{1}{f_s}, \]
with sampling period \( T_s \) and sampling frequency \( f_s \). Each element \( x[k] \) may itself be a vector (e.g., 3D acceleration), so more generally we consider \( x[k] \in \mathbb{R}^n \).
A minimal abstraction for a unidirectional data stream is a tuple
\[ \mathcal{S} = (\{x[k]\}_{k\ge 0},\; f_s,\; \mathcal{T},\; \mathcal{V}), \]
where \( \mathcal{T} \) denotes the transport mechanism (e.g., ROS 2 topics, shared memory, CAN bus) and \( \mathcal{V} \) the visualization operator mapping the sequence into plots or dashboards.
At integration time, we must ensure:
- Sampling consistency: each sensor uses a well-defined \( f_s \).
- Time synchronization: timestamps come from a consistent clock.
- Transport capacity: links can support the aggregate data rate.
- Visualization fidelity: plots accurately represent dynamics without aliasing or misleading resampling.
flowchart TD
P["Physical world (continuous x(t))"]
--> SAMP["Sensor & ADC (sampling)"]
--> NODE["Driver / Node (discrete x[k])"]
--> BUS["Middleware bus (topics / messages)"]
--> LOG["Logger (bag / file)"]
--> VIS["Visualization (plots / dashboards)"]
VIS --> FB["Human interpretation"]
FB --> TUNE["Controller & parameter tuning"]
2. Mathematical Model of Streaming and Latency
Consider a chain of processing blocks applied to a sampled signal \( x[k] \):
\[ x[k] \xrightarrow{\mathcal{F}_1} y_1[k] \xrightarrow{\mathcal{F}_2} y_2[k] \cdots \xrightarrow{\mathcal{F}_m} y_m[k], \]
where each \( \mathcal{F}_i \) is a (possibly nonlinear) operator. For streaming operation, we typically constrain each \( \mathcal{F}_i \) to be causal:
\[ y_i[k] = \mathcal{F}_i\big(x[0], x[1], \dots, x[k]\big), \]
i.e., the output at time step \( k \) uses only past and present inputs.
Let each processing block contribute a (possibly fractional) delay \( d_i \) (measured in multiples of \( T_s \) or in seconds). The total end-to-end latency from measurement to visualization is then
\[ D_{\text{tot}} = \sum_{i=1}^m d_i. \]
In many robot debugging scenarios, we require \( D_{\text{tot}} \) to be well below the dominant time constant of the mechanical dynamics so that the plotted signals approximate real-time behavior from the operator's point of view.
When the processing is linear and time-invariant (LTI), each block can be represented as a convolution:
\[ y_i[k] = \sum_{\ell=0}^{\infty} h_i[\ell]\, x[k-\ell], \]
with impulse response \( h_i[\ell] \). The overall chain remains LTI with effective impulse response
\[ h_{\text{tot}}[\ell] = (h_m * h_{m-1} * \dots * h_1)[\ell]. \]
This allows us to use familiar linear control and signal processing tools (e.g., z-transform, Bode plots) to reason about how streaming filters distort or delay signals prior to visualization.
3. Sampling Frequency, Bandwidth, and Aliasing
Suppose a continuous-time signal has effective one-sided bandwidth \( B \) (Hz), meaning its Fourier transform \( X(\omega) \) is approximately zero for \( |\omega| > 2\pi B \). The Shannon sampling theorem states that if the sampling frequency satisfies
\[ f_s \ge 2 B, \]
then the continuous-time signal can be reconstructed perfectly from the discrete-time samples (in the absence of quantization and noise).
If \( f_s \) is too low, higher-frequency components fold into lower frequencies, causing aliasing. In a robotics context, aliasing can make vibration or high-frequency noise appear as slower oscillations in plots, potentially leading to incorrect controller tuning.
To mitigate aliasing, we typically apply an analog or digital low-pass filter with cutoff \( f_c \) and choose
\[ f_c \le \frac{f_s}{2}, \quad f_s \ge 2 B_{\text{dyn}}, \]
where \( B_{\text{dyn}} \) is the highest frequency relevant to the robot's mechanical dynamics (which is often much smaller than raw sensor bandwidth).
The student who knows basic linear control can therefore interpret sensor plots in the frequency domain: peaks near the Nyquist frequency \( f_s / 2 \) should be treated cautiously, especially when loggers or dashboards downsample the stream for display.
4. Middleware Pipelines and Throughput Analysis
Consider a topic-based middleware (e.g., ROS 2, LCM) where a publisher produces messages at period \( T_p \) and each message has size \( S \) bytes. The average data rate on this topic is
\[ R = \frac{S}{T_p} \quad [\text{bytes/s}]. \]
For a link with capacity \( C \) bytes/s, stability of the transport layer (no unbounded queue growth) requires
\[ \sum_{j} R_j \le C, \]
where the sum is over all topics sharing the link.
Now consider a single subscriber which processes messages with average service time \( T_s^{\text{proc}} \). Modeling the subscriber queue as an \( M/M/1 \)-like system, we require the service rate \( \mu = 1 / T_s^{\text{proc}} \) to exceed the arrival rate \( \lambda = 1 / T_p \):
\[ \rho = \frac{\lambda}{\mu} = \frac{T_s^{\text{proc}}}{T_p} < 1 \quad \Rightarrow \quad T_s^{\text{proc}} < T_p. \]
When \( \rho \) approaches 1, the expected queue length and latency grow rapidly, which in practice appears as delayed plots or dropped samples in visualization tools.
flowchart TD
PUB["Publisher (period T_p)"]
--> Q["Queue (buffer size N)"]
--> SUB["Subscriber (service time T_s_proc)"]
--> VIS["Visualizer"]
Q --> DROP{"Buffer full?"}
DROP -->|yes| LOSS["Drop newest / oldest sample"]
DROP -->|no| SUB
5. Visualization Operators on Time-Series Data
A visualization can be formalized as a mapping from raw data to a graphical object (line plot, histogram, spectrogram, etc.). For a scalar time series \( x[k] \), a sliding-window plot of the last \( N \) samples is the operator
\[ \mathcal{V}_N: \{x[k]\}_{k\ge 0} \mapsto \big\{(t_{k-\ell}, x[k-\ell])\big\}_{\ell=0}^{N-1}. \]
If we add a smoothing filter (e.g., moving average) before plotting,
\[ \tilde{x}[k] = \frac{1}{M} \sum_{\ell=0}^{M-1} x[k-\ell], \]
then the visualization actually represents \( \tilde{x}[k] \), not \( x[k] \). This smoothing is equivalent to an FIR filter with frequency response
\[ H(e^{j\omega}) = \frac{1}{M} \sum_{\ell=0}^{M-1} e^{-j\omega \ell} = \frac{1}{M}\, \frac{1 - e^{-j\omega M}}{1 - e^{-j\omega}}, \]
which attenuates high-frequency content. Students should remember from control theory that such filtering affects both magnitude and phase, and therefore the apparent overshoot and settling time in plots.
6. Python Lab — Streaming and Live Plotting
In Python, a common robotics pattern is to subscribe to a middleware
topic (e.g., ROS 2
/joint_states) and update a live plot. Below is a minimal
conceptual example using ROS 2 (rclpy) and
matplotlib for a single joint angle stream.
import collections
import threading
import time
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState
import matplotlib.pyplot as plt
WINDOW_SIZE = 200
class JointPlotNode(Node):
def __init__(self):
super().__init__("joint_plot_node")
self.buffer_t = collections.deque(maxlen=WINDOW_SIZE)
self.buffer_q = collections.deque(maxlen=WINDOW_SIZE)
self.sub = self.create_subscription(
JointState, "/joint_states", self.callback, 10
)
def callback(self, msg):
# assume first joint
t = msg.header.stamp.sec + msg.header.stamp.nanosec * 1e-9
q = msg.position[0]
self.buffer_t.append(t)
self.buffer_q.append(q)
def plotting_thread(node):
plt.ion()
fig, ax = plt.subplots()
line, = ax.plot([], [])
ax.set_xlabel("time [s]")
ax.set_ylabel("joint position [rad]")
while rclpy.ok():
if node.buffer_t:
t0 = node.buffer_t[0]
t_vals = [ti - t0 for ti in node.buffer_t]
q_vals = list(node.buffer_q)
line.set_xdata(t_vals)
line.set_ydata(q_vals)
ax.relim()
ax.autoscale_view()
plt.draw()
plt.pause(0.01)
else:
plt.pause(0.01)
def main():
rclpy.init()
node = JointPlotNode()
th = threading.Thread(target=plotting_thread, args=(node,), daemon=True)
th.start()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()
Mathematically, this implements the windowed visualization operator \( \mathcal{V}_N \) introduced earlier, with \( N = \text{WINDOW\_SIZE} \). The plotting rate (here around 100 Hz) must be high enough relative to the data rate to preserve apparent smoothness, but low enough to avoid overloading the CPU and introducing additional latency.
7. C++ Lab — ROS 2 Subscriber with Logging
In C++, robotics middleware is often accessed through ROS 2
(rclcpp). Here we implement a subscriber which logs
streaming IMU data to a CSV file. This log can be visualized offline
using Python, MATLAB, or dedicated tools.
#include <chrono>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/imu.hpp>
using std::placeholders::_1;
using ImuMsg = sensor_msgs::msg::Imu;
class ImuLoggerNode : public rclcpp::Node {
public:
ImuLoggerNode()
: Node("imu_logger_node"), outfile_("imu_log.csv", std::ios::out)
{
outfile_ << "t,ax,ay,az" << std::endl;
sub_ = this->create_subscription<ImuMsg>(
"/imu/data", 50, std::bind(&ImuLoggerNode::callback, this, _1));
}
~ImuLoggerNode() {
outfile_.close();
}
private:
void callback(const ImuMsg::SharedPtr msg) {
double t = static_cast<double>(msg->header.stamp.sec) +
1e-9 * static_cast<double>(msg->header.stamp.nanosec);
double ax = msg->linear_acceleration.x;
double ay = msg->linear_acceleration.y;
double az = msg->linear_acceleration.z;
outfile_ << t << "," << ax << "," << ay << "," << az << std::endl;
}
rclcpp::Subscription<ImuMsg>::SharedPtr sub_;
std::ofstream outfile_;
};
int main(int argc, char **argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<ImuLoggerNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
If the IMU publishes at \( f_p \) Hz and CSV writing takes bounded time, the logger effectively approximates an ideal sampling and storage operator. Offline plotting then becomes a pure visualization operator acting on a finite sequence \( \{x[k]\}_{k=0}^{N-1} \).
8. Java Lab — Streaming and Visualization Skeleton
Java is less dominant in robotics than C++/Python, but middleware such as ROSJava or LCM allows Java-based visualization tools. The following simplified skeleton demonstrates how a Java application might consume a stream of scalar samples (e.g., from a network socket) and push them into a thread-safe buffer for plotting in a GUI framework.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.concurrent.ConcurrentLinkedQueue;
public class StreamConsumer {
private final ConcurrentLinkedQueue<Sample> buffer =
new ConcurrentLinkedQueue<>();
private static class Sample {
final double t;
final double value;
Sample(double t, double value) {
this.t = t;
this.value = value;
}
}
public void startReader(String host, int port) throws Exception {
Socket socket = new Socket(host, port);
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
new Thread(() -> {
String line;
long t0 = System.nanoTime();
try {
while ((line = br.readLine()) != null) {
double val = Double.parseDouble(line.trim());
double t = 1e-9 * (System.nanoTime() - t0);
buffer.add(new Sample(t, val));
}
} catch (Exception e) {
e.printStackTrace();
}
}, "stream-reader").start();
}
public ConcurrentLinkedQueue<Sample> getBuffer() {
return buffer;
}
// GUI code (e.g. JavaFX) would periodically read from buffer and update plots.
}
The mathematical abstraction is identical: a time-stamped sequence \( (t_k, x[k]) \) is accumulated and passed to a visualization operator. Java's concurrency primitives (queues, locks) help maintain causal order and bounded latency in the presence of GUI event loops.
9. MATLAB/Simulink Lab — Streaming from ROS and Scopes
MATLAB and Simulink provide direct support for ROS topics via Robotics
System Toolbox. Online visualization is typically carried out using the
timeseries type, plotting commands, or Simulink Scope
blocks.
% Initialize ROS connection (ROS master already running)
rosinit;
% Subscribe to a scalar topic, e.g., "/joint1_position"
sub = rossubscriber("/joint1_position");
N = 500;
t_vec = zeros(N, 1);
q_vec = zeros(N, 1);
for k = 1:N
msg = receive(sub); % blocking receive
t_vec(k) = double(msg.Header.Stamp.Sec) + ...
1e-9 * double(msg.Header.Stamp.Nsec);
q_vec(k) = msg.Data;
end
% Shift time origin
t_vec = t_vec - t_vec(1);
figure;
plot(t_vec, q_vec, "LineWidth", 1.5);
xlabel("time [s]");
ylabel("joint position [rad]");
grid on;
% Shut down ROS node
rosshutdown;
In Simulink, an equivalent streaming visualization uses the
Subscribe block (for ROS topics) connected to
Scope or Time Scope blocks. This implements
the same windowed operator \( \mathcal{V}_N \) in
block-diagram form, consistent with students' control-systems intuition.
10. Mathematical Addendum — Moving Average as LTI Filter
Many visualization tools include a simple moving-average option to reduce noise. We show that the moving average of length \( M \)
\[ y[k] = \frac{1}{M} \sum_{\ell=0}^{M-1} x[k-\ell] \]
is a linear and time-invariant operator:
- Linearity: for any signals \( x_1[k], x_2[k] \) and scalars \( a, b \),
\[ \begin{aligned} \mathcal{M}\{a x_1[k] + b x_2[k]\} &= \frac{1}{M} \sum_{\ell=0}^{M-1} \big(a x_1[k-\ell] + b x_2[k-\ell]\big) \\ &= a\, \mathcal{M}\{x_1[k]\} + b\, \mathcal{M}\{x_2[k]\}. \end{aligned} \]
- Time invariance: delaying input by \( k_0 \) samples yields
\[ \begin{aligned} \mathcal{M}\{x[k-k_0]\} &= \frac{1}{M} \sum_{\ell=0}^{M-1} x[(k-k_0)-\ell] \\ &= y[k-k_0], \end{aligned} \]
which is just the delayed version of the original filtered output. Thus this visualization-friendly smoothing operation is an LTI filter whose behavior can be analyzed with classical tools.
11. Problems and Solutions
Problem 1 (Nyquist Rate for Joint Encoder): A robot joint has significant dynamics up to 8 Hz. You wish to stream and visualize the joint position without aliasing. What is the minimum sampling frequency \( f_s \) you should choose according to the Shannon sampling theorem? Suggest a practical value used in real systems.
Solution: With bandwidth \( B = 8 \) Hz, the sampling theorem requires
\[ f_s \ge 2 B = 16\ \text{Hz}. \]
However, in practice we want some safety margin and room for filter roll-off. Choosing \( f_s = 50 \) Hz or \( f_s = 100 \) Hz is common, giving roughly 6 to 12 samples per highest-frequency cycle and yielding visually smooth plots and more reliable discrete-time models.
Problem 2 (Throughput and Link Capacity): A LiDAR publishes frames of size 50 kB at 20 Hz, and an IMU publishes 80-byte messages at 200 Hz. Both are transported over a link with capacity 5 Mbit/s. Compute the combined data rate, and check whether the link is saturated.
Solution: The LiDAR data rate is
\[ R_{\text{LiDAR}} = 50\,\text{kB} \times 20\,\text{Hz} = 1000\,\text{kB/s} = 8\,\text{Mbit/s}. \]
The IMU data rate is
\[ R_{\text{IMU}} = 80\,\text{B} \times 200\,\text{Hz} = 16000\,\text{B/s} \approx 128\,\text{kbit/s}. \]
Thus the combined rate is approximately
\[ R_{\text{tot}} \approx 8.128\,\text{Mbit/s}. \]
Since the link capacity is only 5 Mbit/s, the condition \( \sum_j R_j \le C \) is violated, so the link is overloaded and packets will be delayed or dropped. In practice, we would reduce LiDAR frame rate, compress data, or increase link capacity.
Problem 3 (Queue Stability in a Visualization Node): A visualization node receives messages at period \( T_p = 5 \) ms and processes each message in \( T_s^{\text{proc}} = 3 \) ms on average. Using the utilization factor \( \rho = T_s^{\text{proc}} / T_p \), determine whether the queue is stable. What happens if processing time increases to 6 ms?
Solution: With \( T_p = 5 \) ms and \( T_s^{\text{proc}} = 3 \) ms,
\[ \rho = \frac{T_s^{\text{proc}}}{T_p} = \frac{3}{5} = 0.6 < 1, \]
so the queue is stable on average. If processing time increases to \( T_s^{\text{proc}} = 6 \) ms, then
\[ \rho = \frac{6}{5} = 1.2 > 1, \]
which means the service rate is lower than the arrival rate and the queue length grows without bound (ignoring finite-buffer drops). Practically, this leads to arbitrarily large visualization lag or frequent sample loss.
Problem 4 (Moving Average and Group Delay): Consider the length-\( M \) moving average \( y[k] = \frac{1}{M} \sum_{\ell=0}^{M-1} x[k-\ell] \). Show that the effective group delay introduced by this filter is \( (M-1)/2 \) samples for low frequencies.
Solution: The impulse response is \( h[\ell] = 1/M \) for \( \ell = 0,1,\dots,M-1 \) and zero otherwise. The center of mass of this symmetric window is
\[ \ell_c = \frac{\sum_{\ell=0}^{M-1} \ell\, h[\ell]}{\sum_{\ell=0}^{M-1} h[\ell]} = \frac{\frac{1}{M} \sum_{\ell=0}^{M-1} \ell}{1} = \frac{1}{M} \cdot \frac{(M-1)M}{2} = \frac{M-1}{2}. \]
For low frequencies (where phase is approximately linear in frequency), this center of mass corresponds to the group delay. Thus the moving average delays the signal by about \( (M-1)/2 \) samples, which should be accounted for when interpreting visualized trajectories.
Problem 5 (Effect of Downsampling on Visualization): A ROS bag contains joint data sampled at 1 kHz, but your visualization tool displays only every 10th sample for performance reasons (downsampling by \( L = 10 \)). What is the new effective sampling frequency? How does this affect the highest frequency that can be visualized without aliasing?
Solution: Downsampling by \( L \) reduces the sampling frequency by a factor of \( L \):
\[ f_s^{\text{new}} = \frac{f_s}{L} = \frac{1000}{10} = 100\ \text{Hz}. \]
The new Nyquist frequency is
\[ f_{\text{Nyq}}^{\text{new}} = \frac{f_s^{\text{new}}}{2} = 50\ \text{Hz}. \]
Thus, any motion components above 50 Hz will be aliased in the visualization, even though the underlying bag file stored the full 1 kHz information. This illustrates why visualization downsampling must be designed carefully when studying high-frequency robot dynamics (e.g., vibrations).
12. Summary
In this integration-focused lesson, we modeled streaming robot data as sampled signals, derived basic conditions for aliasing-free visualization, and analyzed end-to-end latency and throughput of middleware pipelines. We interpreted visualization tools as operators on discrete-time sequences and examined the moving-average filter as an LTI system with quantifiable frequency response and group delay. Finally, we connected these abstractions to practical implementations in Python, C++, Java, and MATLAB/Simulink that students can adapt to their own robot stacks.
13. References
- Shannon, C. E. (1949). Communication in the presence of noise. Proceedings of the IRE, 37(1), 10–21.
- Nyquist, H. (1928). Certain topics in telegraph transmission theory. Transactions of the AIEE, 47(2), 617–644.
- Oppenheim, A. V., & Schafer, R. W. (1969). Discrete-time signal processing. IEEE Transactions on Education, E-12(3), 123–134. (Foundational article-style exposition.)
- Wornell, G. W. (1996). Wavelet-based representations for the data compression of communication signals. IEEE Transactions on Information Theory, 42(3), 713–731.
- Muthukrishnan, S. (2005). Data streams: Algorithms and applications. Foundations and Trends in Theoretical Computer Science, 1(2), 117–236.
- Cleveland, W. S., & McGill, R. (1984). Graphical perception: Theory, experimentation, and application to the development of graphical methods. Journal of the American Statistical Association, 79(387), 531–554.
- Brillinger, D. R. (1981). Time series: Data analysis and theory. SIAM Regional Conference Series in Applied Mathematics, 36.
- Åström, K. J., & Wittenmark, B. (1984). Computer controlled systems: Theoretical considerations. IEEE Transactions on Automatic Control, 29(1), 1–10.
- Walden, R. H. (1999). Analog-to-digital converter survey and analysis. IEEE Journal on Selected Areas in Communications, 17(4), 539–550.
- Stoica, P., & Moses, R. L. (1997). Introduction to spectral analysis. Prentice Hall Signal Processing Series. (Monograph-style, theory-focused reference.)