diff --git a/setup.py b/setup.py index 2176054..c42357c 100644 --- a/setup.py +++ b/setup.py @@ -120,7 +120,7 @@ def get_version(): def get_install_requires(): if 'FLATPAK_INSTALL' in os.environ: - install_requires=['cycler', 'matplotlib', 'numpy', + install_requires=['cycler', 'matplotlib', 'numpy==1.26.4', 'py', 'pyparsing', 'pyqt5', 'pytest', 'python-dateutil', 'pytz', 'six', 'scipy', diff --git a/src/installer.cfg b/src/installer.cfg index a3cfc10..a2630d2 100644 --- a/src/installer.cfg +++ b/src/installer.cfg @@ -1,7 +1,7 @@ [Application] name=rdplot publisher=IENT -version=1.0 +version=1.3.4 # How to launch the app - this calls the 'main' function from the 'myapp' package: entry_point=rdplot.__main__:main icon=rdplot/logo/plot512_0wd_icon.ico diff --git a/src/rdplot/SimulationDataItem.py b/src/rdplot/SimulationDataItem.py index 36d25be..8820714 100644 --- a/src/rdplot/SimulationDataItem.py +++ b/src/rdplot/SimulationDataItem.py @@ -25,7 +25,7 @@ from os import listdir from os.path import join, abspath, isfile, isdir, basename, splitext from PyQt5.QtCore import * -from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QComboBox, QPushButton, QDialogButtonBox, QLabel, QCheckBox, QGroupBox, QMessageBox, QApplication +from PyQt5.QtWidgets import QDialog, QVBoxLayout, QComboBox, QDialogButtonBox, QLabel, QCheckBox import re @@ -457,7 +457,7 @@ def create_item_list_from_directory(self, directory_path): except SimulationDataItemError as error: pass # We definitely cannot accept thousands of exceptions on the command line - # print((AbstractEncLog + # print(( # "Could not create simulation data item from file '{}'" # " due to {}" # ).format(path, error)) @@ -522,7 +522,7 @@ def parse_csv_item_list(self, log_path): continue item_list.append(CSVLog(config, header, line)) return item_list - except: + except Exception as e: raise SimulationDataItemError() # Magic Methods diff --git a/src/rdplot/SimulationDataItemClasses/CsvLogs.py b/src/rdplot/SimulationDataItemClasses/CsvLogs.py index 2668bd4..58c1675 100644 --- a/src/rdplot/SimulationDataItemClasses/CsvLogs.py +++ b/src/rdplot/SimulationDataItemClasses/CsvLogs.py @@ -32,7 +32,15 @@ def __init__(self, config, header, line): header = header.replace("\n", "") header = re.split(r'[,;]',header.lower()) header = list(filter(None, header)) - sequence_idx = header.index("sequence") + + try: + sequence_idx = header.index("sequence") + except Exception: # Sequence not found. Search for partial string + sequence_idx = 0 + for i, token in enumerate(header): + if "sequence" in token: + sequence_idx = i + break try: qp_idx = header.index("qp") diff --git a/src/rdplot/SimulationDataItemClasses/DatLogs.py b/src/rdplot/SimulationDataItemClasses/DatLogs.py index 9220e1f..4193a7a 100644 --- a/src/rdplot/SimulationDataItemClasses/DatLogs.py +++ b/src/rdplot/SimulationDataItemClasses/DatLogs.py @@ -20,7 +20,7 @@ import xmltodict from abc import abstractmethod from xml.parsers.expat import ExpatError -from os.path import normpath, basename, sep, dirname +from os.path import normpath, dirname from rdplot.SimulationDataItem import (AbstractSimulationDataItem, SimulationDataItemError) diff --git a/src/rdplot/SimulationDataItemClasses/EncoderLogs.py b/src/rdplot/SimulationDataItemClasses/EncoderLogs.py index 9f59816..771fb2b 100644 --- a/src/rdplot/SimulationDataItemClasses/EncoderLogs.py +++ b/src/rdplot/SimulationDataItemClasses/EncoderLogs.py @@ -259,7 +259,6 @@ def _parse_summary_data(self): vals = [float(val) for val in vals] # convert to numbers name_val_dict = dict(zip(names, vals)) # pack both together in a dict - # print(summary_type) name_rate = 'Bitrate' if summary_type == 'SUMMARY': diff --git a/src/rdplot/Widgets/MainWindow.py b/src/rdplot/Widgets/MainWindow.py index c839f29..47a719a 100644 --- a/src/rdplot/Widgets/MainWindow.py +++ b/src/rdplot/Widgets/MainWindow.py @@ -246,6 +246,7 @@ def remove(self): self._selection_model.selectionChanged.connect(self.change_list) self._variable_tree_selection_model.selectionChanged.connect(self.update_plot) if len(self.selectedSimulationDataItemListModel.values()) == 0: + self.plotPreview.reset_plot_color_cycle() self.update_plot() def change_list(self, q_selected, q_deselected): @@ -396,7 +397,7 @@ def check_labels(self): selectionmodel.clearSelection() # updates the plot if the plot variable is changed - def update_plot(self, force=False): + def update_plot(self, force=True): # user-generated curves and curves loaded from files are not supposed to be mixed user_generated_curves = False if self.sender() == self._variable_tree_selection_model or self.sender() == self.curveListSelectionModel or force: diff --git a/src/rdplot/Widgets/PlotWidget.py b/src/rdplot/Widgets/PlotWidget.py index 0152312..a6c267a 100644 --- a/src/rdplot/Widgets/PlotWidget.py +++ b/src/rdplot/Widgets/PlotWidget.py @@ -86,12 +86,22 @@ def __init__(self, ): self.anchor_identifier = '' self.ci_mode = 'average' + self.reset_plot_color_cycle() + + self.ci_visible = False + + def reset_plot_color_cycle(self): + ''' + Reset the linestyles and color cycle for plotting + ''' self.color_cycle = ['r', 'b', 'y', 'k', 'c', 'm', 'g', 'r', 'b', 'y', 'k', 'c', 'm', 'g'] - self.marker_cycle = ['x', 'x', 'x', 'x', 'x', 'x', 'x', 'o', 'o', 'o', 'o', 'o', 'o', 'o'] + self.marker_cycle = ['x', 'o', 'v', '2', 's', 'P', '*', 'D', 'h'] self.linestyle_cycle = ["-", "--", ":", "-."] self.plot_index = 0 + self.marker_index = 0 self.plot_linestyle_index = 0 self.color_list = [] + self.marker_list = [] self.linestyle_list = [ ("psnr y", self.linestyle_cycle[0]), ("psnr u", self.linestyle_cycle[1]), ("psnr v", self.linestyle_cycle[2]), ("wpsnr y", self.linestyle_cycle[0]), ("wpsnr u", self.linestyle_cycle[1]), ("wpsnr v", self.linestyle_cycle[2]), @@ -100,9 +110,6 @@ def __init__(self, ): ("mos", self.linestyle_cycle[0]), ] - self.ci_visible = False - - def create_legend(self, plot_data_collection): tmp_legend = [] for plot_data in plot_data_collection: @@ -121,20 +128,35 @@ def create_legend(self, plot_data_collection): return legend - - def set_color(self, name): + def set_color(self, name, sequence): + color = None for color_item in self.color_list: if color_item[0] == name: - return (color_item[1], color_item[2]) + color = color_item[1] + + marker = None + for marker_item in self.marker_list: + if marker_item[0] == sequence: + marker = marker_item[1] - color = self.color_cycle[self.plot_index] - marker = self.marker_cycle[self.plot_index] + if color is None: + color = self.color_cycle[self.plot_index] + + self.plot_index += 1 + if(self.plot_index >= len(self.color_cycle)): + self.plot_index = 0 + + self.color_list.append([name, color]) + + if marker is None: + marker = self.marker_cycle[self.marker_index] - self.plot_index += 1 - if(self.plot_index >= len(self.color_cycle)): - self.plot_index = 0 + self.marker_index += 1 + if(self.marker_index >= len(self.marker_cycle)): + self.marker_index = 0 + + self.marker_list.append([sequence, marker]) - self.color_list.append([name, color, marker]) return (color, marker) def set_linestyle(self, path): @@ -155,7 +177,6 @@ def plot_confidence_interval(self, ax, x, y, ci, color): for i in range(len(x)): top = y[i] + ci[i] bottom = y[i] - ci[i] - print(x[i], top, bottom) ax.plot([x[i], x[i]], [top, bottom], color=color, marker="_", ms=8, solid_capstyle="butt") #, alpha=0.3) # refreshes the figure according to new changes done @@ -167,6 +188,14 @@ def change_plot(self, plot_data_collection, user_generated_curves=False): temporal data """ + try: + if(len(plot_data_collection) == 1): + self.label_2.setText(plot_data_collection[0].identifiers[0]) + else: + self.label_2.setText("Plot Area") + except Exception: + self.label_2.setText("Plot Area") + # Set the anchor identifier for the first time # if no identifier has been set so far (similar # to the selection in BdTableModel update method) @@ -229,6 +258,28 @@ def change_plot(self, plot_data_collection, user_generated_curves=False): for plot_data in plot_data_collection: legend.append(plot_data.identifiers[0]) + # Get min and max for reference plotting + minr = 1e100 + maxr = -minr + miny = 1e100 + maxy = -miny + for plot_data in plot_data_collection: + if not plot_data.has_ci: + values = ((float(x), float(y)) for (x, y) in plot_data.values) + sorted_value_pairs = sorted(values, key=lambda pair: pair[0]) + [xs, ys] = list(zip(*sorted_value_pairs)) + else: + # A confidence interval is included in the data + values = ((float(x), float(y), float(z)) for (x, y, z) in plot_data.values) + sorted_value_pairs = sorted(values, key=lambda pair: pair[0]) + [xs, ys, zs] = list(zip(*sorted_value_pairs)) + if np.isnan(min(xs)) or np.isnan(max(xs)): + continue + minr = min(min(xs), minr) + maxr = max(max(xs), maxr) + miny = min(min(ys), miny) + maxy = max(max(ys), maxy) + # plot all the lines which are missing yet plot_count = 0 for plot_data in plot_data_collection: @@ -236,7 +287,7 @@ def change_plot(self, plot_data_collection, user_generated_curves=False): l = legend[plot_count] #" ".join([i for i in plot_data.identifiers] + plot_data.path) if plot_data.color == " ": - (plot_data.color, plot_data.marker) = self.set_color(plot_data.identifiers[1]) + (plot_data.color, plot_data.marker) = self.set_color(plot_data.identifiers[1], plot_data.identifiers[0]) plot_data.linestyle = self.set_linestyle(plot_data.path[1]) # Convert list of pairs of strings to two sorted lists of floats @@ -249,6 +300,10 @@ def change_plot(self, plot_data_collection, user_generated_curves=False): sorted_value_pairs = sorted(values, key=lambda pair: pair[0]) [xs, ys] = list(zip(*sorted_value_pairs)) + if len(xs) == 1 and np.isnan(xs[0]): + xs = [minr, maxr] + ys = [ys[0], ys[0]] + # plot the current plot data curve = self.ax.plot(xs, ys, label=l, color=plot_data.color, marker=plot_data.marker, linestyle=plot_data.linestyle) @@ -259,6 +314,11 @@ def change_plot(self, plot_data_collection, user_generated_curves=False): sorted_value_pairs = sorted(values, key=lambda pair: pair[0]) [xs, ys, zs] = list(zip(*sorted_value_pairs)) + if len(xs) == 1 and np.isnan(xs[0]): + xs = [minr, maxr] + ys = [ys[0], ys[0]] + zs = [zs[0], zs[0]] + # calculate the lower and upper boundaries of the CI ys_low = np.subtract(ys, zs) ys_up = np.add(ys, zs) @@ -288,14 +348,32 @@ def change_plot(self, plot_data_collection, user_generated_curves=False): self.plot_confidence_interval(self.ax, xs, ys, zs, plot_data.color) plot_count += 1 - except: - sys.stderr.write("Too many values for confidence interval. Please only add one value.") + except Exception as e: + print(e) # Set the legend if not(legend == ['']): self.ax.legend(loc='lower right') DataCursor(self.ax.get_lines()) + # Specific to MOS plotting + try: + if plot_data_collection[0].label[1].lower() == "mos": + if miny >= 1 and maxy <= 5: # 5-grade scale + self.ax.set_ylim(1, 5) + elif miny >= 0 and maxy <= 10: # 11-grade scale + self.ax.set_ylim(0, 10) + elif miny >= 0 and maxy <= 100: # 101-grade scale + self.ax.set_ylim(0, 100) + elif miny >= -1 and maxy <= 1: # the other one :D + self.ax.set_ylim(-1, 1) + elif miny >= -2 and maxy <= 2: # the other one :D + self.ax.set_ylim(-2, 2) + elif miny >= -3 and maxy <= 3: # the other one :D + self.ax.set_ylim(-3, 3) + except Exception as e: + print(e) + start, end = self.ax.get_ylim() data_range = end - start # get ticks with decimal precision diff --git a/src/rdplot/__main__.py b/src/rdplot/__main__.py index 5945c65..cbe2a3d 100644 --- a/src/rdplot/__main__.py +++ b/src/rdplot/__main__.py @@ -52,4 +52,5 @@ def main(): sys.exit(app.exec_()) if __name__ == '__main__': + print("Starting RD-Plot...") main() \ No newline at end of file diff --git a/src/rdplot/model.py b/src/rdplot/model.py index b4c6246..0843b67 100644 --- a/src/rdplot/model.py +++ b/src/rdplot/model.py @@ -19,7 +19,6 @@ ################################################################################################## from collections import deque from os.path import sep -from os import environ import numpy as np from PyQt5 import QtWidgets, QtCore from PyQt5.QtWidgets import QPushButton @@ -794,7 +793,6 @@ def update(self, sim_data_items, check_add_param = True): if hasattr(sim_data_item, 'qp') and not QP_added: self.dialog.chosen_par.addItems(['QP']) QP_added = True - # print(sim_data_item.summary_data['encoder_config']) value_filter = ['.yuv', '.bin', '.hevc', '.jem'] key_filter = [] for sim_class in all_log_configs.keys(): diff --git a/src/rdplot/ui/mainWindow.ui b/src/rdplot/ui/mainWindow.ui index a27ea48..400f516 100644 --- a/src/rdplot/ui/mainWindow.ui +++ b/src/rdplot/ui/mainWindow.ui @@ -263,13 +263,6 @@ padding: 4px; - - - - Plot CI as area - - - @@ -301,6 +294,13 @@ padding: 4px; + + + + Plot CI as area + + + diff --git a/src/rdplot/view.py b/src/rdplot/view.py index 1161c42..8fbfd64 100644 --- a/src/rdplot/view.py +++ b/src/rdplot/view.py @@ -67,9 +67,14 @@ def run(self): try: sim_data_items = self._factory.create_item_list_from_path(path) print("Parsed '{}' ".format(path)) - except SimulationDataItemError: + except SimulationDataItemError as error: self.newParsedData.emit([]) self.path_list.clear() + + # print(( + # "Could not create simulation data item from file '{}'" + # " due to {}" + # ).format(path, error)) return self.newParsedData.emit(sim_data_items) @@ -117,9 +122,14 @@ def run(self): try: sim_data_items = self._factory.create_item_list_from_path(path) print("Parsed '{}' ".format(path)) - except SimulationDataItemError: + except SimulationDataItemError as error: self.newParsedData.emit([]) self.path_list.clear() + + # print(( + # "Could not create simulation data item from file '{}'" + # " due to {}" + # ).format(path, error)) return self.newParsedData.emit(sim_data_items)