Created
August 19, 2022 22:05
-
-
Save Kautenja/89635c43816b02f68074060b371c9fb7 to your computer and use it in GitHub Desktop.
A structure for rendering plots in the style of matplotlib using opencv.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""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