
Python offers many useful features in its standard libraries. Hiding in the collections library, there’s a lesser known data structure, the deque [1] **** (apparently pronounced _dec_k). Let me show you what it is, how it works and why it is a perfect data structure for plotting continuous data from a stream!
Basics
Deque is a data type imported from the collections module, it stands for double ended queue. Essentially it is a list with two ends, so you can easily add/remove from the head or tail.

To use it we declare a variable for the queue. Since the queue has two ends, we can easily append to both ends with either append to add to the tail or appendleft to add to the beginning of the list.
from collections import deque
my_queue = deque()
my_queue.append("a")
my_queue.append("b")
my_queue.appendleft("c")
my_queue.appendleft("d")
print(my_queue) # deque(['d', 'c', 'a', 'b'])
You can also pop from both ends of the queue with either pop for the right side or popleft for removing elements from the start.

my_queue.pop()
print(my_queue) # deque(['d', 'c', 'a'])
my_queue.popleft()
print(my_queue) # deque(['c', 'a'])
Limited Length Buffer
The real power of the deque comes to show, once we limit the length of the queue. In the constructor we can optionally supply a max length integer for the queue, which is by default None and the list is not limited in size. The interesting part is the behaviour once we append to either side of the list when it’s full. The list will shift all items away from the inserted end and pop the last item on the other end of the queue.

So if we append on the right side, all elements shift to the left by one index and the first element on the left side gets discarded. In the following example, we start with three elements "a,b,c" in order in a deque of length three, so it is full at that point. If we add another element "d" at the end using append, the first element "a" will be removed to keep the list at the max length of 3.
my_queue = deque(maxlen=3)
my_queue.append("a")
my_queue.append("b")
my_queue.append("c")
print(my_queue) # deque(['a', 'b', 'c'], maxlen=3)
my_queue.append("d")
print(my_queue) # deque(['b', 'c', 'd'], maxlen=3)
Accordingly if we append to the left side, the elements shift to the right and the last item at the end is popped. So if we append an "a" at the left end of the queue, we push out the last element, which is the "d" and end up again with the sequence "a,b,c" in the queue.
... # deque(['b', 'c', 'd'], maxlen=3)
my_queue.appendleft("a")
print(my_queue) # deque(['a', 'b', 'c'], maxlen=3)
Plotting Data Stream
Now let’s see a practical example with a usage of the deque data structure for Data Visualization. I am going to use my webcam stream, where we want to track the position of the green ball on the screen.

Since the input is an indefinitely long stream as long as we keep the webcam video input open, we cannot simply keep adding tracked positions to a list, as this would just continuously grow in size. Instead, the goal is to show a trail for the trajectory of the ball with a limited amount of historic points, like in the sketch below.

The setup for this project is taken from an earlier project, you can find the full source code at the end. The important part is that we create a deque variable for the position tracking with a limited length.
tracked_pos = deque(maxlen=20)
...
x, y, w, h = cv2.boundingRect(largest_contour)
center = (x + w // 2, y + h // 2)
tracked_pos.append(center)
That’s actually all we need, the queue will only keep the last 20 points. When adding a new point when the list is already at full capacity, the oldest point will just be popped. That way, we can achieve the snake-like trailing effect. To draw the trajectory, we simply iterate over pairs
for i in range(1, len(tracked_pos)):
cv2.line(
frame_annotated,
pt1=tracked_pos[i - 1],
pt2=tracked_pos[i],
color=(255, 0, 0),
thickness=1,
)

We can also write this loop in a more pythonic way using the zip function (no import needed) and iterating directly over pairs of trajectory points. However, to do the array slicing to offset the iterable for the second point, we need to convert the deque to a list, otherwise we will get an error.
# tracked_pos[1:] -> this throws an error!
# TypeError: sequence index must be integer, not 'slice'
# -> array slicing does NOT work on dequeus
# to slice, need to convert to list first
tracked_pos_list = list(tracked_pos)
for p1, p2 in zip(tracked_pos_list, tracked_pos_list[1:]):
cv2.line(frame_annotated, pt1=p1, pt2=p2, color=(255, 0, 0), thickness=1)
An even better way to do this iteration is by using the pairwise [2] function from the itertools library. This is also part of the standard library in python and doesn’t require any additional pip packages.
from itertools import pairwise
...
for p1, p2 in pairwise(tracked_pos):
cv2.line(frame_annotated, pt1=p1, pt2=p2, color=(255, 0, 0), thickness=1)
The output is still the same, but this is a much cleaner way to write the same functionality as above.
Rainbow Trail
For some more beauty in our animation, we can use a rainbow colormap to use different colors for the parts of the trajectory. By enumerating the pairwise iteration, we can get an index for each of the elements in the trajectory. Then by dividing that index by the length of the list, we can get a float in the range [0, 1], where 1 represents the most recent element and 0 the oldest position. Now we simply need a function that takes this value and converts it to a color.
def colormap_rainbow(value: float) -> tuple[int, int, int]:
"""Map a value between 0 and 1 to a color in the rainbow colormap."""
# TODO: implement rainbow colors
return (255, 255, 255)
...
traj_len = len(tracked_pos)
for i, (p1, p2) in enumerate(pairwise(tracked_pos)):
color = color_map_rainbow(i / traj_len)
cv2.line(frame_annotated, pt1=p1, pt2=p2, color=color, thickness=1)
For the function to map the float to a color we can use the built-in colormaps from OpenCV-Python, specifically the rainbow colormap cv2.COLORMAP_RAINBOW [3].

Since the function expects an image to apply the colormap to, we need to use some trickery to get this to work. We create a 1×1 image with a single color channel that contains the float value rescaled to a uint8 in the range [0, 255], as this is the expected input format for images in OpenCV.
pixel_img = np.array([[value * 255]], dtype=np.uint8)
# shape: (1, 1)
Next we can apply the rainbow colormap, which will return again a 1×1 image but with 3 color channels, containing the corresponding BGR values from the rainbow colormap.
pixel_cmap_img = cv2.applyColorMap(pixel_img, cv2.COLORMAP_RAINBOW)
# shape: (1, 1, 3)
To return a tuple of three integers, we need to remove the dimensions of the image by flattening it and then converting it from a numpy array to a list.
def colormap_rainbow(value: float) -> tuple[int, int, int]:
"""Map a value between 0 and 1 to a color in the rainbow colormap."""
pixel_img = np.array([[value * 255]], dtype=np.uint8)
# shape: (1, 1)
pixel_cmap_img = cv2.applyColorMap(pixel_img, cv2.COLORMAP_RAINBOW)
# shape: (1, 1, 3)
return pixel_cmap_img.flatten().tolist()
Now if we run our program again, we can see our rainbow trajectory!

Conclusion
In this article, I introduced you to the built-in data type deque in Python and showed you how to use it for visualization of continuous data, such as a video stream from a webcam or a real-time security camera.
The full source code for this project is available in the ball tracking repository on GitHub, in the src/stream.py file. Take care!
GitHub – trflorian/ball-tracking-live-plot: Tracking a ball using OpenCV and plotting the…
[1] https://docs.python.org/3/library/collections.html#collections.deque. [2] https://docs.python.org/3/library/itertools.html#itertools.pairwise [3] https://docs.opencv.org/4.x/d3/d50/groupimgproccolormap.html
All visualizations in this post were created by the author.