You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
408 lines
14 KiB
408 lines
14 KiB
#! /usr/bin/env python3 |
|
""" |
|
function collection for plotting |
|
""" |
|
|
|
from typing import Optional, List, Tuple, Dict |
|
|
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
from matplotlib.pyplot import Figure, Axes |
|
from matplotlib.backends.backend_pdf import PdfPages |
|
|
|
|
|
def get_min_arg_time_value( |
|
time_series_data: np.ndarray, data_time: np.ndarray) -> Tuple[int, float, float]: |
|
""" |
|
:param time_series_data: |
|
:param data_time: |
|
:return: |
|
""" |
|
min_arg = np.argmin(time_series_data) |
|
min_time = data_time[min_arg] |
|
min_value = np.amin(time_series_data) |
|
return (min_arg, min_value, min_time) |
|
|
|
|
|
def get_max_arg_time_value( |
|
time_series_data: np.ndarray, data_time: np.ndarray) -> Tuple[int, float, float]: |
|
""" |
|
:param time_series_data: |
|
:param data_time: |
|
:return: |
|
""" |
|
max_arg = np.argmax(time_series_data) |
|
max_time = data_time[max_arg] |
|
max_value = np.amax(time_series_data) |
|
return max_arg, max_value, max_time |
|
|
|
|
|
class DataPlot(object): |
|
""" |
|
A plotting class interface. Provides functions such as saving the figure. |
|
""" |
|
def __init__( |
|
self, plot_data: Dict[str, np.ndarray], variable_names: List[List[str]], |
|
plot_title: str = '', sub_titles: Optional[List[str]] = None, |
|
x_labels: Optional[List[str]] = None, y_labels: Optional[List[str]] = None, |
|
y_lim: Optional[Tuple[int, int]] = None, legend: Optional[List[str]] = None, |
|
pdf_handle: Optional[PdfPages] = None) -> None: |
|
""" |
|
Initializes the data plot class interface. |
|
:param plot_title: |
|
:param pdf_handle: |
|
""" |
|
self._plot_data = plot_data |
|
self._variable_names = variable_names |
|
self._plot_title = plot_title |
|
self._sub_titles = sub_titles |
|
self._x_labels = x_labels |
|
self._y_labels = y_labels |
|
self._y_lim = y_lim |
|
self._legend = legend |
|
self._pdf_handle = pdf_handle |
|
self._fig = None |
|
self._ax = None |
|
self._fig_size = (20, 13) |
|
|
|
@property |
|
def fig(self) -> Figure: |
|
""" |
|
:return: the figure handle |
|
""" |
|
if self._fig is None: |
|
self._create_figure() |
|
return self._fig |
|
|
|
@property |
|
def ax(self) -> Axes: |
|
""" |
|
:return: the axes handle |
|
""" |
|
if self._ax is None: |
|
self._create_figure() |
|
return self._ax |
|
|
|
@property |
|
def plot_data(self) -> dict: |
|
""" |
|
returns the plot data. calls _generate_plot_data if necessary. |
|
:return: |
|
""" |
|
if self._plot_data is None: |
|
self._generate_plot_data() |
|
return self._plot_data |
|
|
|
def plot(self) -> None: |
|
""" |
|
placeholder for the plotting function. A child class should implement this function. |
|
:return: |
|
""" |
|
|
|
def _create_figure(self) -> None: |
|
""" |
|
creates the figure handle. |
|
:return: |
|
""" |
|
self._fig, self._ax = plt.subplots(frameon=True, figsize=self._fig_size) |
|
self._fig.suptitle(self._plot_title) |
|
|
|
|
|
def _generate_plot_data(self) -> None: |
|
""" |
|
placeholder for a function that generates a data table necessary for plotting |
|
:return: |
|
""" |
|
|
|
def show(self) -> None: |
|
""" |
|
displays the figure on the screen. |
|
:return: None |
|
""" |
|
self.fig.show() |
|
|
|
|
|
def save(self) -> None: |
|
""" |
|
saves the figure if a pdf_handle was initialized. |
|
:return: |
|
""" |
|
|
|
if self._pdf_handle is not None and self.fig is not None: |
|
self.plot() |
|
self._pdf_handle.savefig(figure=self.fig) |
|
else: |
|
print('skipping saving to pdf: handle was not initialized.') |
|
|
|
|
|
def close(self) -> None: |
|
""" |
|
closes the figure. |
|
:return: |
|
""" |
|
plt.close(self._fig) |
|
|
|
|
|
class TimeSeriesPlot(DataPlot): |
|
""" |
|
class for creating multiple time series plot. |
|
""" |
|
def __init__( |
|
self, plot_data: dict, variable_names: List[List[str]], x_labels: List[str], |
|
y_labels: List[str], plot_title: str = '', sub_titles: Optional[List[str]] = None, |
|
pdf_handle: Optional[PdfPages] = None) -> None: |
|
""" |
|
initializes a timeseries plot |
|
:param plot_data: |
|
:param variable_names: |
|
:param xlabels: |
|
:param ylabels: |
|
:param plot_title: |
|
:param pdf_handle: |
|
""" |
|
super().__init__( |
|
plot_data, variable_names, plot_title=plot_title, sub_titles=sub_titles, |
|
x_labels=x_labels, y_labels=y_labels, pdf_handle=pdf_handle) |
|
|
|
def plot(self): |
|
""" |
|
plots the time series data. |
|
:return: |
|
""" |
|
if self.fig is None: |
|
return |
|
|
|
for i in range(len(self._variable_names)): |
|
plt.subplot(len(self._variable_names), 1, i + 1) |
|
for v in self._variable_names[i]: |
|
plt.plot(self.plot_data[v], 'b') |
|
plt.xlabel(self._x_labels[i]) |
|
plt.ylabel(self._y_labels[i]) |
|
|
|
self.fig.tight_layout(rect=[0, 0.03, 1, 0.95]) |
|
|
|
|
|
class InnovationPlot(DataPlot): |
|
""" |
|
class for creating an innovation plot. |
|
""" |
|
def __init__( |
|
self, plot_data: dict, variable_names: List[Tuple[str, str]], x_labels: List[str], |
|
y_labels: List[str], plot_title: str = '', sub_titles: Optional[List[str]] = None, |
|
pdf_handle: Optional[PdfPages] = None) -> None: |
|
""" |
|
initializes a timeseries plot |
|
:param plot_data: |
|
:param variable_names: |
|
:param xlabels: |
|
:param ylabels: |
|
:param plot_title: |
|
:param sub_titles: |
|
:param pdf_handle: |
|
""" |
|
super().__init__( |
|
plot_data, variable_names, plot_title=plot_title, sub_titles=sub_titles, |
|
x_labels=x_labels, y_labels=y_labels, pdf_handle=pdf_handle) |
|
|
|
|
|
def plot(self): |
|
""" |
|
plots the Innovation data. |
|
:return: |
|
""" |
|
|
|
if self.fig is None: |
|
return |
|
|
|
for i in range(len(self._variable_names)): |
|
# create a subplot for every variable |
|
plt.subplot(len(self._variable_names), 1, i + 1) |
|
if self._sub_titles is not None: |
|
plt.title(self._sub_titles[i]) |
|
|
|
# plot the value and the standard deviation |
|
plt.plot( |
|
1e-6 * self.plot_data['timestamp'], self.plot_data[self._variable_names[i][0]], 'b') |
|
plt.plot( |
|
1e-6 * self.plot_data['timestamp'], |
|
np.sqrt(self.plot_data[self._variable_names[i][1]]), 'r') |
|
plt.plot( |
|
1e-6 * self.plot_data['timestamp'], |
|
-np.sqrt(self.plot_data[self._variable_names[i][1]]), 'r') |
|
|
|
plt.xlabel(self._x_labels[i]) |
|
plt.ylabel(self._y_labels[i]) |
|
plt.grid() |
|
|
|
# add the maximum and minimum value as an annotation |
|
_, max_value, max_time = get_max_arg_time_value( |
|
self.plot_data[self._variable_names[i][0]], 1e-6 * self.plot_data['timestamp']) |
|
_, min_value, min_time = get_min_arg_time_value( |
|
self.plot_data[self._variable_names[i][0]], 1e-6 * self.plot_data['timestamp']) |
|
|
|
plt.text( |
|
max_time, max_value, 'max={:.2f}'.format(max_value), fontsize=12, |
|
horizontalalignment='left', |
|
verticalalignment='bottom') |
|
plt.text( |
|
min_time, min_value, 'min={:.2f}'.format(min_value), fontsize=12, |
|
horizontalalignment='left', |
|
verticalalignment='top') |
|
|
|
self.fig.tight_layout(rect=[0, 0.03, 1, 0.95]) |
|
|
|
|
|
class ControlModeSummaryPlot(DataPlot): |
|
""" |
|
class for creating a control mode summary plot. |
|
""" |
|
|
|
def __init__( |
|
self, data_time: np.ndarray, plot_data: dict, variable_names: List[List[str]], |
|
x_label: str, y_labels: List[str], annotation_text: List[str], |
|
additional_annotation: Optional[List[str]] = None, plot_title: str = '', |
|
sub_titles: Optional[List[str]] = None, |
|
pdf_handle: Optional[PdfPages] = None) -> None: |
|
""" |
|
initializes a timeseries plot |
|
:param plot_data: |
|
:param variable_names: |
|
:param xlabels: |
|
:param ylabels: |
|
:param plot_title: |
|
:param sub_titles: |
|
:param pdf_handle: |
|
""" |
|
super().__init__( |
|
plot_data, variable_names, plot_title=plot_title, sub_titles=sub_titles, |
|
x_labels=[x_label]*len(y_labels), y_labels=y_labels, pdf_handle=pdf_handle) |
|
self._data_time = data_time |
|
self._annotation_text = annotation_text |
|
self._additional_annotation = additional_annotation |
|
|
|
|
|
def plot(self): |
|
""" |
|
plots the control mode data. |
|
:return: |
|
""" |
|
|
|
if self.fig is None: |
|
return |
|
|
|
colors = ['b', 'r', 'g', 'c'] |
|
|
|
for i in range(len(self._variable_names)): |
|
# create a subplot for every variable |
|
plt.subplot(len(self._variable_names), 1, i + 1) |
|
if self._sub_titles is not None: |
|
plt.title(self._sub_titles[i]) |
|
|
|
for col, var in zip(colors[:len(self._variable_names[i])], self._variable_names[i]): |
|
plt.plot(self._data_time, self.plot_data[var], col) |
|
|
|
plt.xlabel(self._x_labels[i]) |
|
plt.ylabel(self._y_labels[i]) |
|
plt.grid() |
|
plt.ylim(-0.1, 1.1) |
|
|
|
for t in range(len(self._annotation_text[i])): |
|
|
|
_, _, align_time = get_max_arg_time_value( |
|
np.diff(self.plot_data[self._variable_names[i][t]]), self._data_time) |
|
v_annot_pos = (t+1.0)/(len(self._variable_names[i])+1) # vert annotation position |
|
|
|
if np.amin(self.plot_data[self._variable_names[i][t]]) > 0: |
|
plt.text( |
|
align_time, v_annot_pos, |
|
'no pre-arm data - cannot calculate {:s} start time'.format( |
|
self._annotation_text[i][t]), fontsize=12, horizontalalignment='left', |
|
verticalalignment='center', color=colors[t]) |
|
elif np.amax(self.plot_data[self._variable_names[i][t]]) > 0: |
|
plt.text( |
|
align_time, v_annot_pos, '{:s} at {:.1f} sec'.format( |
|
self._annotation_text[i][t], align_time), fontsize=12, |
|
horizontalalignment='left', verticalalignment='center', color=colors[t]) |
|
|
|
if self._additional_annotation is not None: |
|
for a in range(len(self._additional_annotation[i])): |
|
v_annot_pos = (a + 1.0) / (len(self._additional_annotation[i]) + 1) |
|
plt.text( |
|
self._additional_annotation[i][a][0], v_annot_pos, |
|
self._additional_annotation[i][a][1], fontsize=12, |
|
horizontalalignment='left', verticalalignment='center', color='b') |
|
|
|
self.fig.tight_layout(rect=[0, 0.03, 1, 0.95]) |
|
|
|
|
|
class CheckFlagsPlot(DataPlot): |
|
""" |
|
class for creating a control mode summary plot. |
|
""" |
|
|
|
def __init__( |
|
self, data_time: np.ndarray, plot_data: dict, variable_names: List[List[str]], |
|
x_label: str, y_labels: List[str], y_lim: Optional[Tuple[int, int]] = None, |
|
plot_title: str = '', legend: Optional[List[str]] = None, |
|
sub_titles: Optional[List[str]] = None, pdf_handle: Optional[PdfPages] = None, |
|
annotate: bool = False) -> None: |
|
""" |
|
initializes a timeseries plot |
|
:param plot_data: |
|
:param variable_names: |
|
:param xlabels: |
|
:param ylabels: |
|
:param plot_title: |
|
:param sub_titles: |
|
:param pdf_handle: |
|
""" |
|
super().__init__( |
|
plot_data, variable_names, plot_title=plot_title, sub_titles=sub_titles, |
|
x_labels=[x_label]*len(y_labels), y_labels=y_labels, y_lim=y_lim, legend=legend, |
|
pdf_handle=pdf_handle) |
|
self._data_time = data_time |
|
self._b_annotate = annotate |
|
|
|
|
|
def plot(self): |
|
""" |
|
plots the control mode data. |
|
:return: |
|
""" |
|
|
|
if self.fig is None: |
|
return |
|
|
|
colors = ['b', 'r', 'g', 'c', 'k', 'm'] |
|
|
|
for i in range(len(self._variable_names)): |
|
# create a subplot for every variable |
|
plt.subplot(len(self._variable_names), 1, i + 1) |
|
if self._sub_titles is not None: |
|
plt.title(self._sub_titles[i]) |
|
|
|
for col, var in zip(colors[:len(self._variable_names[i])], self._variable_names[i]): |
|
plt.plot(self._data_time, self.plot_data[var], col) |
|
|
|
plt.xlabel(self._x_labels[i]) |
|
plt.ylabel(self._y_labels[i]) |
|
plt.grid() |
|
if self._y_lim is not None: |
|
plt.ylim(self._y_lim) |
|
|
|
if self._legend is not None: |
|
plt.legend(self._legend[i], loc='upper left') |
|
|
|
if self._b_annotate: |
|
for col, var in zip(colors[:len(self._variable_names[i])], self._variable_names[i]): |
|
# add the maximum and minimum value as an annotation |
|
_, max_value, max_time = get_max_arg_time_value( |
|
self.plot_data[var], self._data_time) |
|
mean_value = np.mean(self.plot_data[var]) |
|
|
|
plt.text( |
|
max_time, max_value, |
|
'max={:.4f}, mean={:.4f}'.format(max_value, mean_value), color=col, |
|
fontsize=12, horizontalalignment='left', verticalalignment='bottom') |
|
|
|
self.fig.tight_layout(rect=[0, 0.03, 1, 0.95])
|
|
|