Skip to content

Instantly share code, notes, and snippets.

@Kautenja
Created August 19, 2022 22:05
Show Gist options
  • Save Kautenja/89635c43816b02f68074060b371c9fb7 to your computer and use it in GitHub Desktop.
Save Kautenja/89635c43816b02f68074060b371c9fb7 to your computer and use it in GitHub Desktop.
A structure for rendering plots in the style of matplotlib using opencv.
"""Structures for real-time plot rendering using OpenCV."""
import cv2
import numpy as np
from matplotlib import pyplot as plt
class Plot:
"""A base structure for real-time plot rendering using OpenCV."""
def __init__(self, title: str,
width: int=400,
height: int=200,
min_: int=None,
max_: int=None,
bipolar: bool=True,
buffer_length: int=None,
background_color: tuple=(0.0, 0.0, 0.0),
axis_color: tuple=(0.1, 0.1, 0.1),
tracker_color: tuple=(0.0, 1.0, 1.0),
margin: tuple=(5, 5, 5, 5),
cmap=plt.cm.tab10,
) -> None:
"""
Initialize a new plotter.
Args:
title: The title of the plot window to generate.
width: The width of the window to generate.
height: The height of the window to generate.
min_: The minimum value for the plot axes.
max_: The maximum value for the plot axes.
bipolar: True for bipolar data, False for unipolar data.
buffer_length: The length of the buffer to generate. Defaults to
the width when None.
background_color: The color of the background pixels.
axis_color: The color for the zero-axis line.
tracker_color: The color for the tracker on the head of plot lines.
margin: The margin in (x, -x, y, -y) format.
cmap: The color map to use when rendering individual lines.
Returns:
None
"""
self.title = title
self.width = width
self.height = height
self.min_ = min_
self.max_ = max_
self.bipolar = bipolar
self.buffer_length = self.width if buffer_length is None else buffer_length
self.background_color = background_color
self.axis_color = axis_color
self.tracker_color = tracker_color
self.margin = margin
self.cmap = cmap
# Initialize internal data structure for maintaining plot state.
self._canvas = np.zeros((self.height, self.width, 3))
self._plots = {}
def plot(self, data: dict) -> None:
"""
Update the plot state with new data.
Args:
data: A dictionary of the data to update the plot state with. Each
key is expected to be the name of the plot and each value an
integer.
Returns:
None
"""
# Reset the canvas for the plots to zeros
self._canvas[:] = self.background_color
# Update the internal buffers with the new state.
for label, value in data.items():
if label not in self._plots: # First call to this plot.
# Create a buffer of values for this plot.
self._plots[label] = self.buffer_length * [0]
self._plots[label].append(int(value))
if len(self._plots[label]) > self.buffer_length:
self._plots[label] = self._plots[label][-self.buffer_length:]
margin_l, margin_r, margin_u, margin_d = self.margin
# Plot the zero axis.
cv2.line(self._canvas,
(margin_l, int((self.height-margin_d-margin_u)/(1 + self.bipolar))+margin_u ),
(self.width-margin_r, int((self.height-margin_d-margin_u)/(1 + self.bipolar))+margin_u),
self.axis_color, 1)
# Calculate the vertical scale factor.
scale_h_min = self.min_ if self.bipolar else 0
if scale_h_min is None:
scale_h_min = abs(min(list(map(min, self._plots.values()))))
scale_h_max = self.max_
if scale_h_max is None:
scale_h_max = max(scale_h_min, max(list(map(max, self._plots.values()))))
scale_h = ((self.height-margin_d-margin_u)/(1 + self.bipolar))/scale_h_max if not scale_h_max == 0 else 0
# Plot the data points for the plots.
for idx, label in enumerate(sorted(data.keys())):
for j, i in enumerate(np.linspace(0, self.buffer_length-2, self.width-margin_l-margin_r).astype(int)):
cv2.line(self._canvas,
(j+margin_l, int((self.height-margin_d-margin_u)/(1 + self.bipolar) + margin_u - self._plots[label][i] * scale_h)),
(j+margin_l, int((self.height-margin_d-margin_u)/(1 + self.bipolar) + margin_u - self._plots[label][i+1] * scale_h)),
self.cmap(idx), 1, lineType=cv2.LINE_AA)
# Plot a circle indicating the head of the plot-line.
cv2.circle(self._canvas, (self.width-margin_r, int(margin_u + (self.height-margin_d-margin_u)/(1 + self.bipolar) - self._plots[label][-1]*scale_h)), 2, self.tracker_color, -1)
cv2.imshow(self.title, self._canvas)
if __name__ == '__main__':
import math
unipolar = Plot("Unipolar", 400, 200, buffer_length=200, max_=100, bipolar=False)
bipolar = Plot("Bipolar", 400, 200, buffer_length=200, min_=-100, max_=100, bipolar=True)
for v in range(1,3000):
unipolar.plot({
'sin': int((1 + math.sin(v*math.pi/180))*50),
'cos': int((1 + math.cos(v*math.pi/180))*25),
'sin*cos': int((1 + math.sin(2*v*math.pi/180) * math.cos(v*math.pi/180)) * 50),
})
bipolar.plot({
'sin': int(math.sin(v*math.pi/180)*100),
'cos': int(math.cos(v*math.pi/180)*50),
'sin*cos': int(math.sin(2*v*math.pi/180) * math.cos(v*math.pi/180) * 100),
})
cv2.waitKey(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment