diff --git a/sohstationviewer/database/extract_data.py b/sohstationviewer/database/extract_data.py index 2a49d557064a2e5c78f127f6bc1e9f13c86bc7d4..eef25836e57b2ca3a557aa49edbf2a35b8adeb89 100755 --- a/sohstationviewer/database/extract_data.py +++ b/sohstationviewer/database/extract_data.py @@ -9,13 +9,18 @@ def get_chan_plot_info(org_chan_id: str, data_type: str, color_mode: ColorMode = 'B') -> Dict: """ Given chanID read from raw data file and detected dataType - Return plotting info from DB for that channel + Return plotting info from DB for that channel. :param org_chan_id: channel name read from data source :param chan_info: info of the channel read from data source :param data_type: type of data :param color_mode: B/W - :return info of channel read from DB which is used for plotting + :return chan_db_info[0]: info of channel read from DB which is used for + plotting. In which, + + Key 'dbChannel' keeps channel's name in DB + + Key 'channel' keeps channel's name read from data + + Key 'dbLabel' keeps label value in DB + + Key 'label' keeps label to be displayed in the plotting """ chan = org_chan_id chan = convert_actual_channel_to_db_channel_w_question_mark(chan) @@ -28,8 +33,8 @@ def get_chan_plot_info(org_chan_id: str, data_type: str, # Seeing as we only need one of these columns for a color mode, we only # pull the needed valueColors column from the database. value_colors_column = 'valueColors' + color_mode - o_sql = (f"SELECT channel, plotType, height, unit," - f" convertFactor, label, fixPoint, " + o_sql = (f"SELECT C.param as param, channel as dbChannel, plotType," + f" height, unit, convertFactor, label as dbLabel, fixPoint, " f"{value_colors_column} AS valueColors " f"FROM Channels as C, Parameters as P") if data_type == 'Unknown': @@ -42,13 +47,17 @@ def get_chan_plot_info(org_chan_id: str, data_type: str, if len(chan_db_info) == 0: chan_db_info = execute_db_dict( f"{o_sql} WHERE channel='DEFAULT' and C.param=P.param") + chan_db_info[0]['channel'] = chan_db_info[0]['dbChannel'] else: - if chan_db_info[0]['channel'] == 'SEISMIC': + if chan_db_info[0]['dbChannel'] == 'SEISMIC': seismic_label = get_seismic_chan_label(org_chan_id) chan_db_info[0]['channel'] = org_chan_id + # add plotLabel key to be used in plotting. + # the original key label is unchanged to help when editing the channel's. chan_db_info[0]['label'] = ( - '' if chan_db_info[0]['label'] is None else chan_db_info[0]['label']) + '' if chan_db_info[0]['dbLabel'] is None + else chan_db_info[0]['dbLabel']) chan_db_info[0]['unit'] = ( '' if chan_db_info[0]['unit'] is None else chan_db_info[0]['unit']) chan_db_info[0]['fixPoint'] = ( @@ -63,8 +72,8 @@ def get_chan_plot_info(org_chan_id: str, data_type: str, elif seismic_label is not None: chan_db_info[0]['label'] = seismic_label else: - chan_db_info[0]['label'] = '-'.join([chan_db_info[0]['channel'], - chan_db_info[0]['label']]) + chan_db_info[0]['label'] = '-'.join( + [chan_db_info[0]['channel'], chan_db_info[0]['label']]) if chan_db_info[0]['label'].strip() == 'DEFAULT': chan_db_info[0]['label'] = 'DEFAULT-' + org_chan_id return chan_db_info[0] @@ -179,14 +188,6 @@ def get_params(): return sorted([d[0] for d in param_rows]) -def get_channel_info(chan_id: str, data_type: str): - # get channel info from DB - sql = f"SELECT * FROM Channels " \ - f"WHERE channel='{chan_id}' AND dataType='{data_type}'" - chan_info = execute_db_dict(sql)[0] - return chan_info - - def get_param_info(param: str): # get all info of a param from DB sql = f"SELECT * FROM Parameters WHERE param='{param}'" diff --git a/sohstationviewer/model/general_data/general_data.py b/sohstationviewer/model/general_data/general_data.py index 37f1346795a2eeba6b71f702a1922d56a7469db0..966d40e0080ea0853a92ba163929e719595b3356 100644 --- a/sohstationviewer/model/general_data/general_data.py +++ b/sohstationviewer/model/general_data/general_data.py @@ -15,8 +15,7 @@ from sohstationviewer.view.plotting.gps_plot.gps_point import GPSPoint from sohstationviewer.view.util.enums import LogType from sohstationviewer.model.general_data.general_data_helper import \ retrieve_data_time_from_data_dict, retrieve_gaps_from_data_dict, \ - combine_data, sort_data, squash_gaps, apply_convert_factor_to_data_dict, \ - reset_data + combine_data, sort_data, squash_gaps, reset_data from sohstationviewer.view.create_muti_buttons_dialog import ( create_multi_buttons_dialog) @@ -247,7 +246,6 @@ class GeneralData(): self.sort_all_data() self.combine_all_data() - self.apply_convert_factor_to_data_dicts() self.retrieve_gaps_from_data_dicts() self.retrieve_data_time_from_data_dicts() @@ -408,19 +406,6 @@ class GeneralData(): if data_set_id not in self.log_data: self.log_data[data_set_id] = {} - def apply_convert_factor_to_data_dicts(self): - """ - Applying convert_factor to avoid using flags to prevent double - applying convert factor when plotting - """ - for data_set_id in self.data_set_ids: - apply_convert_factor_to_data_dict( - data_set_id, self.soh_data, self.data_type) - apply_convert_factor_to_data_dict( - data_set_id, self.mass_pos_data, self.data_type) - apply_convert_factor_to_data_dict( - data_set_id, self.waveform_data, self.data_type) - def reset_all_selected_data(self): """ Remove all data_set_ids created in the plotting process. diff --git a/sohstationviewer/model/general_data/general_data_helper.py b/sohstationviewer/model/general_data/general_data_helper.py index 5915968e6859169d8cba4b2d1ce4470ab7bc6028..1c67e7c86471409ed0e7cb3f788890ee1fed3085 100644 --- a/sohstationviewer/model/general_data/general_data_helper.py +++ b/sohstationviewer/model/general_data/general_data_helper.py @@ -3,8 +3,6 @@ import numpy as np import os from pathlib import Path -from sohstationviewer.database.extract_data import get_convert_factor - def _check_related_gaps(min1: float, max1: float, min2: float, max2: float, @@ -172,25 +170,6 @@ def combine_data(selected_data_set_id: Union[str, Tuple[str, str]], }] -def apply_convert_factor_to_data_dict( - selected_data_set_id: Union[str, Tuple[str, str]], - data_dict: Dict, data_type: str) -> None: - """ - Traverse through traces in each channel to convert data according to - convert_factor got from DB - :param selected_data_set_id: the key of the selected data set - :param data_dict: dict of data - :param data_type: type of data - """ - selected_data_dict = data_dict[selected_data_set_id] - for chan_id in selected_data_dict: - channel = selected_data_dict[chan_id] - convert_factor = get_convert_factor(chan_id, data_type) - if convert_factor is not None and convert_factor != 1: - for tr in channel['tracesInfo']: - tr['data'] = convert_factor * tr['data'] - - def reset_data(selected_data_set_id: Union[str, Tuple[str, str]], data_dict: Dict): """ diff --git a/sohstationviewer/model/reftek_data/reftek.py b/sohstationviewer/model/reftek_data/reftek.py index 22fbbe4106bce291bbed3ff5b67033a22da7a1fb..a98438d417c3d552216528933217155fb39e42db 100755 --- a/sohstationviewer/model/reftek_data/reftek.py +++ b/sohstationviewer/model/reftek_data/reftek.py @@ -132,7 +132,6 @@ class RT130(GeneralData): self.sort_all_data() self.combine_all_data() - self.apply_convert_factor_to_data_dicts() retrieve_gaps_from_stream_header( self.stream_header_by_data_set_id_chan, diff --git a/sohstationviewer/view/db_config/add_edit_single_channel_dialog.py b/sohstationviewer/view/db_config/add_edit_single_channel_dialog.py index affd0134ac54f19509f1f7d0ae8df51bad99594e..07dc5866747adfc83fd2dc2513a43877244d1062 100755 --- a/sohstationviewer/view/db_config/add_edit_single_channel_dialog.py +++ b/sohstationviewer/view/db_config/add_edit_single_channel_dialog.py @@ -3,14 +3,19 @@ import platform import os from typing import Optional, Dict +from matplotlib.axes import Axes + from PySide6 import QtWidgets, QtGui from PySide6.QtWidgets import QWidget, QDialog from sohstationviewer.database.process_db import execute_db from sohstationviewer.database.extract_data import ( - get_params, get_channel_info, create_assign_string_for_db_query + get_params, get_chan_plot_info ) +from sohstationviewer.view.plotting.plotting_widget.plotting import Plotting +from sohstationviewer.view.plotting.plotting_widget.plotting_axes import \ + PlottingAxes from sohstationviewer.view.db_config.edit_single_param_dialog import \ EditSingleParamDialog @@ -33,23 +38,25 @@ class AddEditSingleChannelDialog(QDialog): """ Dialog to add info for channel not in database or edit the existing channel """ - def __init__(self, parent: Optional[QWidget], - chan_id: str, data_type: str): + def __init__(self, parent: Optional[QWidget], plotting: Plotting, + chan_id: str, data_type: str, ax: Axes): """ :param parent: the parent widget + :param plotting: object with plotting functions :param chan_id: name of channel to be added/edited :param data_type: type of the data being processed + :param ax: current axes to plot the channel the dialog working on """ self.parent = parent # name of the channel self.chan_id = chan_id # data_type of the channel self.data_type = data_type + self.ax = ax + self.plotting = plotting # param of the channel self.param: str = 'Default' - # True if this channel isn't in DB yet - self.is_new_db_channel: bool = False # To skip on_param_chkbox_changed() when param is changed by the # program at the beginning self.param_changed_by_signal: bool = False @@ -99,8 +106,6 @@ class AddEditSingleChannelDialog(QDialog): self.edit_param_btn = QtWidgets.QPushButton("EDIT PARAMETER", self) # button to save changes to DB self.save_btn = QtWidgets.QPushButton("SAVE CHANNEL", self) - # button to save changes and replot channel - self.save_replot_btn = QtWidgets.QPushButton("SAVE & REPLOT", self) # button to close dialog without doing anything self.cancel_btn = QtWidgets.QPushButton('CANCEL', self) @@ -109,7 +114,8 @@ class AddEditSingleChannelDialog(QDialog): self.connect_signals() def setup_ui(self) -> None: - dlg_type = 'Add' if 'DEFAULT' in self.chan_id else 'Edit' + dlg_type = ('Add' if 'DEFAULT' == self.ax.chan_db_info['channel'] + else 'Edit') self.setWindowTitle(f"{dlg_type} channel {self.chan_id}" f" - {self.data_type}") @@ -146,36 +152,28 @@ class AddEditSingleChannelDialog(QDialog): channel_layout.addWidget(QtWidgets.QLabel('Parameter'), 6, 0, 1, 1) channel_layout.addWidget(self.param_cbobox, 6, 1, 1, 1) - channel_layout.addWidget(self.save_btn, 7, 0, 1, 1) - channel_layout.addWidget(self.save_replot_btn, 7, 1, 1, 1) + channel_layout.addWidget(self.edit_param_btn, 7, 1, 1, 1) - channel_layout.addWidget(self.edit_param_btn, 8, 1, 1, 1) + channel_layout.addWidget(self.save_btn, 8, 1, 1, 1) channel_layout.addWidget(self.cancel_btn, 8, 0, 1, 1) - self.save_replot_btn.setFocus() + self.save_btn.setFocus() def connect_signals(self) -> None: self.param_cbobox.currentTextChanged.connect( self.on_param_cbobox_changed) self.cancel_btn.clicked.connect(self.close) self.save_btn.clicked.connect(self.on_save) - self.save_replot_btn.clicked.connect(self.on_save_replot) self.edit_param_btn.clicked.connect(self.on_edit_param) def set_channel_info(self): """ Add all Channel related info according to information got from DB. - In case Channel isn't in the DB, use the info of DEFAULT channel. Call set_param_info to set Parameter related info. """ - try: - self.channel_info = get_channel_info(self.chan_id, self.data_type) - except IndexError: - self.is_new_db_channel = True - self.channel_info = get_channel_info('DEFAULT', 'Default') - + self.channel_info = self.ax.chan_db_info self.channel_name_lnedit.setText(self.chan_id) - self.label_lnedit.setText(self.channel_info['label']) + self.label_lnedit.setText(self.channel_info['dbLabel']) self.conversion_lnedit.setText( str(float(self.channel_info['convertFactor']))) @@ -212,7 +210,7 @@ class AddEditSingleChannelDialog(QDialog): self.param_changed_by_signal = True self.param_cbobox.setCurrentText(self.param) return - if not self.is_new_db_channel: + if self.channel_info['param'] != 'Default': msg = ("ARE YOU SURE YOU WANT TO CHANGE PARAMETER FOR CHANNEL " f"'{self.chan_id}'?") result = QtWidgets.QMessageBox.question( @@ -227,29 +225,25 @@ class AddEditSingleChannelDialog(QDialog): self.param = new_param self.set_buttons_enabled() - def save(self): + def on_save(self): """ - Save info from GUI to DB + Save info from GUI to DB and replot according to new parameters + except for change in height. """ - if self.is_new_db_channel: + if self.channel_info['channel'] == 'DEFAULT': self.insert_channel_info() else: self.update_channel_info() - def on_save(self): - """ - Save new channel info to DB - """ - self.save() - self.close() - - def on_save_replot(self): - """ - Save new channel info to DB - TODO: Replot the channel in the plotting area - """ - self.save() - print("Do REPLOT") + self.ax.c_data['chan_db_info'] = get_chan_plot_info( + self.channel_name_lnedit.text(), + self.data_type, + self.parent.color_mode + ) + PlottingAxes.clean_axes(self.ax) + self.plotting.plot_channel(self.ax.c_data, + self.channel_name_lnedit.text(), + self.ax) self.close() def set_buttons_enabled(self): @@ -260,11 +254,9 @@ class AddEditSingleChannelDialog(QDialog): if self.param == 'Default': self.edit_param_btn.setEnabled(False) self.save_btn.setEnabled(False) - self.save_replot_btn.setEnabled(False) else: self.edit_param_btn.setEnabled(True) self.save_btn.setEnabled(True) - self.save_replot_btn.setEnabled(True) def on_edit_param(self): """ @@ -284,28 +276,12 @@ class AddEditSingleChannelDialog(QDialog): win = EditSingleParamDialog(self, self.param_cbobox.currentText()) win.exec() - def update_para_info(self, param): - """ - Save parameter related info to Parameters table - :param param: param condition string - """ - plot_type = create_assign_string_for_db_query( - 'plotType', self.plot_type_cbo_box.currentText()) - value_colorb = create_assign_string_for_db_query( - 'valueColorsB', self.value_colorb_widget.text()) - value_colorw = create_assign_string_for_db_query( - 'valueColorsW', self.value_colorw_widget.text()) - height = f"height={self.height_spnbox.value()}" - sql = (f"UPDATE Parameters SET {plot_type}, {value_colorb}, " - f"{value_colorw}, {height} WHERE {param}") - execute_db(sql) - def insert_channel_info(self): sql = ("INSERT INTO Channels VALUES (" f"'{self.channel_name_lnedit.text()}', " f"'{self.label_lnedit.text()}', " f"'{self.param_cbobox.currentText()}', " - f"NULL, " # linkedChan for RT130 only and won't be changed + f"NULL, " # linkedChan won't be used anymore f"{self.conversion_lnedit.text()}, " f"'{self.unit_lnedit.text()}', " f"{self.fix_point_spnbox.value()}, " @@ -320,9 +296,8 @@ class AddEditSingleChannelDialog(QDialog): convert_factor = f"convertFactor={self.conversion_lnedit.text()}" unit = f"unit='{self.unit_lnedit.text()}'" fix_point = f"fixPoint={self.fix_point_spnbox.value()}" - data_type = f"dataType='{self.data_type_lnedit.text()}'" sql = (f"UPDATE Channels SET {label}, {param}, {linked_chan}, " - f"{convert_factor}, {unit}, {fix_point}, {data_type} " + f"{convert_factor}, {unit}, {fix_point} " f"WHERE {channel}") execute_db(sql) diff --git a/sohstationviewer/view/db_config/edit_single_param_dialog.py b/sohstationviewer/view/db_config/edit_single_param_dialog.py index 332996f0a79b515373fd8307f96b6d9fddf05d36..3c38599ea41d27b11e90e13f1217c966f7b34f2e 100755 --- a/sohstationviewer/view/db_config/edit_single_param_dialog.py +++ b/sohstationviewer/view/db_config/edit_single_param_dialog.py @@ -57,6 +57,11 @@ class EditSingleParamDialog(QDialog): self.height_spnbox.setMinimum(0) self.height_spnbox.setMaximum(8) self.height_spnbox.setToolTip("Relative height of the plot") + self.height_warning_label = QtWidgets.QLabel( + "(Height setting will only be applied after RePlot is clicked.)") + self.height_warning_label.setStyleSheet( + "QLabel {color: red; font-size: 10; font-style: italic;}" + ) # button to save change to DB self.save_param_btn = QtWidgets.QPushButton( @@ -91,8 +96,9 @@ class EditSingleParamDialog(QDialog): param_layout.addWidget(QtWidgets.QLabel('Height'), 3, 0, 1, 1) param_layout.addWidget(self.height_spnbox, 3, 1, 1, 1) - param_layout.addWidget(self.cancel_btn, 4, 0, 1, 1) - param_layout.addWidget(self.save_param_btn, 4, 1, 1, 1) + param_layout.addWidget(self.height_warning_label, 4, 0, 1, 2) + param_layout.addWidget(self.cancel_btn, 5, 0, 1, 1) + param_layout.addWidget(self.save_param_btn, 5, 1, 1, 1) def connect_signals(self) -> None: self.plot_type_cbo_box.currentTextChanged.connect(self.set_plot_type) diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting.py b/sohstationviewer/view/plotting/plotting_widget/plotting.py index 59d67efd6f31312375a2570ecf3715b00ef80bf0..828b15f04ba9c737ea5b04fd8c4ca961b0dc7064 100644 --- a/sohstationviewer/view/plotting/plotting_widget/plotting.py +++ b/sohstationviewer/view/plotting/plotting_widget/plotting.py @@ -1,5 +1,5 @@ # class with all plotting functions -from typing import Dict +from typing import Dict, Optional import numpy as np from matplotlib.axes import Axes @@ -9,10 +9,10 @@ from sohstationviewer.view.plotting.plotting_widget.plotting_helper import ( get_categorized_data_from_value_color_equal_on_lower_bound, get_categorized_data_from_value_color_equal_on_upper_bound ) - +from sohstationviewer.view.util.plot_func_names import plot_functions from sohstationviewer.view.util.color import clr from sohstationviewer.view.plotting.plotting_widget.plotting_helper import ( - get_colors_sizes_for_abs_y_from_value_colors + get_colors_sizes_for_abs_y_from_value_colors, apply_convert_factor ) from sohstationviewer.conf import constants @@ -54,7 +54,8 @@ class Plotting: return ax def plot_multi_color_dots_base( - self, c_data: Dict, chan_db_info: Dict, equal_upper: bool = True): + self, c_data: Dict, chan_db_info: Dict, + ax: Optional[Axes] = None, equal_upper: bool = True): """ plot dots in center with colors defined by valueColors in database: Color codes are defined in colorSettings and limitted in 'valColRE' @@ -63,15 +64,17 @@ class Plotting: :param c_data: data of the channel which includes down-sampled (if needed) data in keys 'times' and 'data'. :param chan_db_info: info of channel from DB + :param ax: axes to plot channel :param equal_upper: if True, plot_from_value_color_equal_on_upper_bound will be used otherwise, plot_from_value_color_equal_on_lower_bound will be use :return: ax in which the channel is plotted """ - plot_h = self.plotting_axes.get_height(chan_db_info['height']) - ax = self.plotting_axes.create_axes( - self.parent.plotting_bot, plot_h, - has_min_max_lines=False) + if ax is None: + plot_h = self.plotting_axes.get_height(chan_db_info['height']) + ax = self.plotting_axes.create_axes( + self.parent.plotting_bot, plot_h, + has_min_max_lines=False) if equal_upper: points_list, colors = \ get_categorized_data_from_value_color_equal_on_upper_bound( @@ -83,10 +86,12 @@ class Plotting: # flatten point_list to be x x = [item for row in points_list for item in row] for points, c in zip(points_list, colors): - ax.plot(points, len(points) * [0], linestyle="", - marker='s', markersize=2, - zorder=constants.Z_ORDER['DOT'], - color=clr[c], picker=True, pickradius=3) + chan_plot, = ax.plot( + points, len(points) * [0], linestyle="", + marker='s', markersize=2, + zorder=constants.Z_ORDER['DOT'], + color=clr[c], picker=True, pickradius=3) + ax.chan_plots.append(chan_plot) total_samples = len(x) if len(colors) != 1: @@ -105,25 +110,28 @@ class Plotting: return ax def plot_multi_color_dots_equal_on_upper_bound( - self, c_data: Dict, chan_db_info: Dict, chan_id: str) -> Axes: + self, c_data: Dict, chan_db_info: Dict, + chan_id: str, ax: Optional[Axes] = None) -> Axes: """ Use plot_multi_color_dots_base() to plot channel in which colors are identified by plot_from_value_color_equal_on_upper_bound """ return self.plot_multi_color_dots_base( - c_data, chan_db_info, equal_upper=True) + c_data, chan_db_info, ax, equal_upper=True) def plot_multi_color_dots_equal_on_lower_bound( - self, c_data: Dict, chan_db_info: Dict, chan_id: str) -> Axes: + self, c_data: Dict, chan_db_info: Dict, + chan_id: str, ax: Optional[Axes] = None) -> Axes: """ Use plot_multi_color_dots_base() to plot channel in which colors are identified by plot_from_value_color_equal_on_lower_bound """ return self.plot_multi_color_dots_base( - c_data, chan_db_info, equal_upper=False) + c_data, chan_db_info, ax, equal_upper=False) def plot_tri_colors( - self, c_data: Dict, chan_db_info: Dict, chan_id: str) -> Axes: + self, c_data: Dict, chan_db_info: Dict, + chan_id: str, ax: Optional[Axes] = None) -> Axes: """ Plot 3 different values in 3 lines with 3 different colors according to valueColors: @@ -138,12 +146,14 @@ class Plotting: (if needed) data in keys 'times' and 'data'. :param chan_db_info: info of channel from DB :param chan_id: name of channel + :param ax: axes to plot channel :return ax: axes of the channel """ - plot_h = self.plotting_axes.get_height(chan_db_info['height']) - ax = self.plotting_axes.create_axes( - self.parent.plotting_bot, plot_h, - has_min_max_lines=False) + if ax is None: + plot_h = self.plotting_axes.get_height(chan_db_info['height']) + ax = self.plotting_axes.create_axes( + self.parent.plotting_bot, plot_h, + has_min_max_lines=False) value_colors = chan_db_info['valueColors'].split('|') @@ -157,16 +167,21 @@ class Plotting: times = c_data['times'][0][indexes] # base line - ax.plot([self.parent.min_x, self.parent.max_x], - [val, val], - color=clr['r'], - linewidth=0.5, - zorder=constants.Z_ORDER['CENTER_LINE'] - ) - ax.plot(times, len(times) * [val], linestyle="", - marker='s', markersize=2, - zorder=constants.Z_ORDER['DOT'], - color=clr[c], picker=True, pickradius=3) + line, = ax.plot( + [self.parent.min_x, self.parent.max_x], + [val, val], + color=clr['r'], + linewidth=0.5, + zorder=constants.Z_ORDER['CENTER_LINE'] + ) + ax.chan_plots.append(line) + # dots + dots, = ax.plot( + times, len(times) * [val], linestyle="", + marker='s', markersize=2, + zorder=constants.Z_ORDER['DOT'], + color=clr[c], picker=True, pickradius=3) + ax.chan_plots.append(dots) total_sample_list.append(len(times)) if val == -1: @@ -186,7 +201,8 @@ class Plotting: return ax def plot_up_down_dots( - self, c_data: Dict, chan_db_info: Dict, chan_id: str) -> Axes: + self, c_data: Dict, chan_db_info: Dict, + chan_id: str, ax: Optional[Axes] = None) -> Axes: """ Plot channel with 2 different values, one above, one under center line. Each value has corresponding color defined in valueColors in database. @@ -199,12 +215,14 @@ class Plotting: (if needed) data in keys 'times' and 'data'. :param chan_db_info: info of channel from DB :param chan_id: name of channel + :param ax: axes to plot channel :return ax: axes of the channel """ - plot_h = self.plotting_axes.get_height(chan_db_info['height']) - ax = self.plotting_axes.create_axes( - self.parent.plotting_bot, plot_h, - has_min_max_lines=False) + if ax is None: + plot_h = self.plotting_axes.get_height(chan_db_info['height']) + ax = self.plotting_axes.create_axes( + self.parent.plotting_bot, plot_h, + has_min_max_lines=False) val_cols = chan_db_info['valueColors'].split('|') # up/down has 2 values: 0, 1 which match with index of points_list @@ -222,13 +240,17 @@ class Plotting: colors[val] = c # down dots - ax.plot(points_list[0], len(points_list[0]) * [-0.5], linestyle="", - marker='s', markersize=2, zorder=constants.Z_ORDER['DOT'], - color=clr[colors[0]], picker=True, pickradius=3) + down_dots, = ax.plot( + points_list[0], len(points_list[0]) * [-0.5], linestyle="", + marker='s', markersize=2, zorder=constants.Z_ORDER['DOT'], + color=clr[colors[0]], picker=True, pickradius=3) + ax.chan_plots.append(down_dots) # up dots - ax.plot(points_list[1], len(points_list[1]) * [0.5], linestyle="", - marker='s', markersize=2, zorder=constants.Z_ORDER['DOT'], - color=clr[colors[1]], picker=True, pickradius=3) + up_dots, = ax.plot( + points_list[1], len(points_list[1]) * [0.5], linestyle="", + marker='s', markersize=2, zorder=constants.Z_ORDER['DOT'], + color=clr[colors[1]], picker=True, pickradius=3) + ax.chan_plots.append(up_dots) ax.set_ylim(-2, 2) self.plotting_axes.set_axes_info( @@ -247,7 +269,8 @@ class Plotting: return ax def plot_time_dots( - self, c_data: Dict, chan_db_info: Dict, chan_id: str) -> Axes: + self, c_data: Dict, chan_db_info: Dict, + chan_id: str, ax: Optional[Axes] = None) -> Axes: """ Plot times only :param c_data: dict - data of the channel which includes down-sampled @@ -258,11 +281,13 @@ class Plotting: (if needed) data in keys 'times' and 'data'. :param chan_db_info: info of channel from DB :param chan_id: name of channel + :param ax: axes to plot channel :return ax: axes of the channel """ - plot_h = self.plotting_axes.get_height(chan_db_info['height']) - ax = self.plotting_axes.create_axes( - self.parent.plotting_bot, plot_h) + if ax is None: + plot_h = self.plotting_axes.get_height(chan_db_info['height']) + ax = self.plotting_axes.create_axes( + self.parent.plotting_bot, plot_h) color = 'W' if chan_db_info['valueColors'] not in [None, 'None', '']: @@ -276,16 +301,19 @@ class Plotting: chan_db_info=chan_db_info) for x in x_list: - ax.plot(x, [0] * len(x), marker='s', markersize=1.5, - linestyle='', zorder=constants.Z_ORDER['LINE'], - color=clr[color], picker=True, - pickradius=3) + chan_plot, = ax.plot( + x, [0] * len(x), marker='s', markersize=1.5, + linestyle='', zorder=constants.Z_ORDER['LINE'], + color=clr[color], picker=True, + pickradius=3) + ax.chan_plots.append(chan_plot) ax.x_center = x_list[0] ax.chan_db_info = chan_db_info return ax def plot_lines_dots( - self, c_data: Dict, chan_db_info: Dict, chan_id: str, info: str = '' + self, c_data: Dict, chan_db_info: Dict, chan_id: str, + ax: Optional[Axes] = None, info: str = '' ) -> Axes: """ Plot lines with dots at the data points. Colors of dot and lines are @@ -302,16 +330,19 @@ class Plotting: (if needed) data in keys 'times' and 'data'. :param chan_db_info: info of channel from DB :param chan_id: name of channel + :param ax: axes to plot channel :param info: additional info to be displayed on sub-title under main-title :return ax: axes of the channel """ - plot_h = self.plotting_axes.get_height(chan_db_info['height']) - ax = self.plotting_axes.create_axes( - self.parent.plotting_bot, plot_h) + if ax is None: + plot_h = self.plotting_axes.get_height(chan_db_info['height']) + ax = self.plotting_axes.create_axes( + self.parent.plotting_bot, plot_h) x_list, y_list = c_data['times'], c_data['data'] - + y_list = apply_convert_factor( + y_list, chan_id, self.main_window.data_type) colors = {} if chan_db_info['valueColors'] not in [None, 'None', '']: color_parts = chan_db_info['valueColors'].split('|') @@ -345,14 +376,17 @@ class Plotting: x_list = [x_list[0][top_bottom_index]] y_list = [y_list[0][top_bottom_index]] - ax.myPlot = ax.plot(ax.x_center, [0] * ax.x_center.size, - marker='s', - markersize=1.5, - linestyle='', - zorder=constants.Z_ORDER['DOT'], - mfc=clr[z_color], - mec=clr[z_color], - picker=True, pickradius=3) + chan_plot, = ax.plot( + ax.x_center, [0] * ax.x_center.size, + marker='s', + markersize=1.5, + linestyle='', + zorder=constants.Z_ORDER['DOT'], + mfc=clr[z_color], + mec=clr[z_color], + picker=True, pickradius=3) + ax.chan_plots.append(chan_plot) + info = "GPS Clock Power" else: sample_no_list = [None, sum([len(x) for x in x_list]), None] @@ -374,25 +408,29 @@ class Plotting: # But set pick radius bigger to be easier to click on. # This only apply when sample_no_list[1] > 1 because # when it is 1, need to show dot or nothing will be plotted. - ax.myPlot = ax.plot(x, y, marker='o', markersize=0.01, - linestyle='-', linewidth=0.7, - zorder=constants.Z_ORDER['LINE'], - color=clr[l_color], - picker=True, pickradius=3) + chan_plot, = ax.plot( + x, y, marker='o', markersize=0.01, + linestyle='-', linewidth=0.7, + zorder=constants.Z_ORDER['LINE'], + color=clr[l_color], + picker=True, pickradius=3) else: - ax.myPlot = ax.plot(x, y, marker='s', markersize=1.5, - linestyle='-', linewidth=0.7, - zorder=constants.Z_ORDER['LINE'], - color=clr[l_color], - mfc=clr[d_color], - mec=clr[d_color], - picker=True, pickradius=3) + chan_plot, = ax.plot( + x, y, marker='s', markersize=1.5, + linestyle='-', linewidth=0.7, + zorder=constants.Z_ORDER['LINE'], + color=clr[l_color], + mfc=clr[d_color], + mec=clr[d_color], + picker=True, pickradius=3) + ax.chan_plots.append(chan_plot) ax.chan_db_info = chan_db_info return ax def plot_lines_s_rate( - self, c_data: Dict, chan_db_info: Dict, chan_id: str) -> Axes: + self, c_data: Dict, chan_db_info: Dict, + chan_id: str, ax: Optional[Axes] = None) -> Axes: """ Plot line only for waveform data channel (seismic data). Sample rate unit will be displayed @@ -407,10 +445,12 @@ class Plotting: info = "%dsps" % c_data['samplerate'] else: info = "%gsps" % c_data['samplerate'] - return self.plot_lines_dots(c_data, chan_db_info, chan_id, info=info) + return self.plot_lines_dots( + c_data, chan_db_info, chan_id, ax, info=info) def plot_lines_mass_pos( - self, c_data: Dict, chan_db_info: Dict, chan_id: str) -> Axes: + self, c_data: Dict, chan_db_info: Dict, + chan_id: str, ax: Optional[Axes] = None) -> Axes: """ Plot multi-color dots with grey line for mass position channel. Use get_masspos_value_colors() to get value_colors map based on @@ -420,6 +460,7 @@ class Plotting: (if needed) data in keys 'times' and 'data'. :param chan_db_info: info of channel from DB :param chan_id: name of channel + :param ax: axes to plot channel :return ax: axes of the channel """ value_colors = get_masspos_value_colors( @@ -429,9 +470,10 @@ class Plotting: if value_colors is None: return - plot_h = self.plotting_axes.get_height(chan_db_info['height']) - ax = self.plotting_axes.create_axes( - self.parent.plotting_bot, plot_h) + if ax is None: + plot_h = self.plotting_axes.get_height(chan_db_info['height']) + ax = self.plotting_axes.create_axes( + self.parent.plotting_bot, plot_h) x_list, y_list = c_data['times'], c_data['data'] total_x = sum([len(x) for x in x_list]) @@ -442,16 +484,73 @@ class Plotting: chan_db_info=chan_db_info, y_list=y_list) for x, y in zip(x_list, y_list): # plot to have artist pl.Line2D to get pick - ax.myPlot = ax.plot(x, y, - linestyle='-', linewidth=0.7, - color=self.parent.display_color['sub_basic'], - picker=True, pickradius=3, - zorder=constants.Z_ORDER['LINE'])[0] + ax.plot( + x, y, linestyle='-', linewidth=0.7, + color=self.parent.display_color['sub_basic'], + picker=True, pickradius=3, + zorder=constants.Z_ORDER['LINE']) colors, sizes = get_colors_sizes_for_abs_y_from_value_colors( y, value_colors) - ax.scatter(x, y, marker='s', c=colors, s=sizes, - zorder=constants.Z_ORDER['DOT']) + ax.scatter( + x, y, marker='s', c=colors, s=sizes, + zorder=constants.Z_ORDER['DOT']) + # masspos shouldn't be editable for now so don't append to + # ax.chan_plots. Also scatter is different with ax.plot and + # should be treated in a different way if want to replot. ax.x_center = x_list[0] - ax.y_center = y_list[0] + ax.y_center = apply_convert_factor( + y_list, chan_id, self.main_window.data_type) + ax.chan_db_info = chan_db_info + return ax + + def plot_no_plot_type( + self, c_data: Dict, chan_db_info: Dict, + chan_id: str, ax: Optional[Axes] = None) -> Axes: + """ + In case there're no plot type for the parameter selected when editing + the channel. the channel will be plot with label and no yticklabels and + no data. If users re-edit before replotting, they can change the param + for the channel on the figure. Once they already replot, they have to + go to channel table to change that. + + :param c_data: data of the channel which includes down-sampled + (if needed) data in keys 'times' and 'data'. + :param chan_db_info: info of channel from DB + :param chan_id: name of channel + :param ax: axes to plot channel + :return ax: axes of the channel + """ + + self.plotting_axes.set_axes_info( + ax, + sample_no_list=[], + sample_no_colors=[], + sample_no_pos=[], + chan_db_info=chan_db_info) + ax.chan_db_info = chan_db_info return ax + + def plot_channel(self, c_data: Dict, chan_id: str, + ax: Optional[Axes]) -> Axes: + """ + Plot/replot channel for given data + + :param c_data: data of the channel which includes keys 'times' and + 'data'. Refer to general_data/data_structures.MD + :param chan_id: name of channel + :param ax: axes to plot the channel. If there's no axes provides, + a new axes will be created + :return ax: axes that has been used to plot the channel + """ + if len(c_data['times']) == 0: + return + chan_db_info = c_data['chan_db_info'] + plot_type = chan_db_info['plotType'] + if plot_type in [None, ""]: + # when edit select a param that has no plot type + return self.plot_no_plot_type(c_data, chan_db_info, chan_id, ax) + ax = getattr( + self, plot_functions[plot_type]['plot_function'])( + c_data, chan_db_info, chan_id, ax) + return ax diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting_axes.py b/sohstationviewer/view/plotting/plotting_widget/plotting_axes.py index 6b671ecf38c16a0cc06549cef6edc1bf5fe6143c..eee677abe9c1793f6447644f0db933df9da9a30a 100644 --- a/sohstationviewer/view/plotting/plotting_widget/plotting_axes.py +++ b/sohstationviewer/view/plotting/plotting_widget/plotting_axes.py @@ -143,16 +143,30 @@ class PlottingAxes: fontsize=const.FONTSIZE + 1) timestamp_bar.set_xlim(self.parent.min_x, self.parent.max_x) - def create_axes(self, plot_b, plot_h, has_min_max_lines=True): + @staticmethod + def clean_axes(ax: Axes): + """ + Remove texts and plots on the given axes ax + + :param ax: axes that has texts and plots to be removed + """ + for chan_plot in ax.chan_plots: + chan_plot.remove() + for text in ax.texts: + text.remove() + ax.chan_plots = [] + + def create_axes(self, plot_b: float, plot_h: float, + has_min_max_lines: bool = True) -> Axes: """ Create axes to plot a channel. - :param plot_b: float - bottom of the plot - :param plot_h: float - height of the plot - :param has_min_max_lines: bool - flag showing if the plot need min/max - lines - :return ax: matplotlib.axes.Axes - axes of a channel + :param plot_b: bottom of the plot + :param plot_h: height of the plot + :param has_min_max_lines: flag showing if the plot need min/max lines + :return ax: axes created """ + ax = self.fig.add_axes( [const.PLOT_LEFT_NORMALIZE, plot_b, const.PLOT_WIDTH_NORMALIZE, plot_h], @@ -178,6 +192,8 @@ class PlottingAxes: labelsize=const.FONTSIZE) # transparent background => self.fig will take care of background ax.patch.set_alpha(0) + # prepare chan_plots list to be reference for the plotted lines/dots + ax.chan_plots = [] return ax def create_sample_no_label(self, ax: Axes, pos_y: float, @@ -221,7 +237,8 @@ class PlottingAxes: :param sample_no_colors: list of color to display sample numbers :param sample_no_pos: list of position to display sample numbers [0.05, 0.5, 0.95] are the basic positions. - :param label: title of the plot. If None, show chan_db_info['label'] + :param label: title of the plot. If label is None, use + chan_db_info['label'] :param info: additional info to show in sub title which is smaller and under title on the left side :param y_list: y values of the channel for min/max labels, lines @@ -256,6 +273,9 @@ class PlottingAxes: size=const.FONTSIZE + 1 ) + if not sample_no_pos: + ax.set_yticklabels([]) + return # set samples' total on right side # bottom ax.bottom_total_point_lbl = self.create_sample_no_label( diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting_helper.py b/sohstationviewer/view/plotting/plotting_widget/plotting_helper.py index 23ae00dafe2dccfa1536529c123bcd2032ccb33d..449633141ef57b3dc7ce1173e574b4f6f91aa480 100644 --- a/sohstationviewer/view/plotting/plotting_widget/plotting_helper.py +++ b/sohstationviewer/view/plotting/plotting_widget/plotting_helper.py @@ -1,9 +1,12 @@ from typing import List, Union, Tuple, Dict - +import numpy as np +from copy import copy from sohstationviewer.view.util.enums import LogType from sohstationviewer.conf import constants from sohstationviewer.controller.util import get_val from sohstationviewer.view.util.color import clr +from sohstationviewer.database.extract_data import get_convert_factor + # TODO: put this in DB mass_pos_volt_ranges = {"regular": [0.5, 2.0, 4.0, 7.0], @@ -187,3 +190,20 @@ def get_colors_sizes_for_abs_y_from_value_colors( # The last value color colors[i] = clr[c] return colors, sizes + + +def apply_convert_factor(data: List[np.ndarray], chan_id: str, data_type: str + ) -> np.ndarray: + """ + Convert data according to convert_factor got from DB + + :param data: list of value array + :param chan_id: name of channel + :param data_type: type of data + """ + convert_factor = get_convert_factor(chan_id, data_type) + if convert_factor is not None and convert_factor != 1: + new_data = [convert_factor * copy(data[0])] + return new_data + else: + return data diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py b/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py index 6412a1bc655f01d635e27d425c6c323a13713956..a5953fedef438fe071bbdae0433f7abe13d85b84 100644 --- a/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py +++ b/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py @@ -172,6 +172,11 @@ class PlottingWidget(QtWidgets.QScrollArea): # List of SOH message lines in RT130 to display in info box when # there're more than 2 lines for one data point clicked self.rt130_log_data: Optional[List[str]] = None + + """ + log_idxes: line index of RT130's log messages + """ + self.log_idxes = None # ---------------------------------------------------------------- QtWidgets.QScrollArea.__init__(self) @@ -342,6 +347,7 @@ class PlottingWidget(QtWidgets.QScrollArea): """ self.is_button_press_event_triggered_pick_event = True artist = event.artist + self.log_idxes = None if not isinstance(artist, pl.Line2D): return ax = artist.axes @@ -381,25 +387,16 @@ class PlottingWidget(QtWidgets.QScrollArea): f"Time: {formatted_clicked_time} " f"Value: {clicked_data}</pre>") if 'logIdx' in chan_data.keys(): - log_idxes = [chan_data['logIdx'][0][idx] - for idx in real_idxes] + self.log_idxes = [chan_data['logIdx'][0][idx] + for idx in real_idxes] if len(real_idxes) > 1: info_str = info_str.replace( "</pre>", f" ({len(real_idxes)} lines)") - for idx in log_idxes: + for idx in self.log_idxes: info_str += ( "<pre> " + self.rt130_log_data[idx] + "</pre>") + display_tracking_info(self.tracking_box, info_str) - if 'logIdx' in chan_data.keys(): - # For Reftek, need to hightlight the corresponding - # SOH message lines based on the log_idxes of the clicked point - self.parent.search_message_dialog.show() - try: - self.parent.search_message_dialog. \ - show_log_entry_from_log_indexes(log_idxes) - except ValueError as e: - QtWidgets.QMessageBox.warning(self, "Not found", - str(e)) def on_button_press_event(self, event): """ diff --git a/sohstationviewer/view/plotting/state_of_health_widget.py b/sohstationviewer/view/plotting/state_of_health_widget.py index d00ad0af167f462dfd3d56ab2cd04bd4c6861ca3..f9743033a895c51540829187fd090f2c8d8a9062 100644 --- a/sohstationviewer/view/plotting/state_of_health_widget.py +++ b/sohstationviewer/view/plotting/state_of_health_widget.py @@ -1,14 +1,17 @@ # Drawing State-Of-Health channels and mass position -from typing import Tuple, Union, Dict - -from sohstationviewer.view.util.plot_func_names import plot_functions +from typing import Tuple, Union, Dict, Optional +from matplotlib.axes import Axes +from matplotlib.backend_bases import MouseButton +from PySide6 import QtWidgets, QtCore from sohstationviewer.model.general_data.general_data import GeneralData from sohstationviewer.view.util.enums import LogType from sohstationviewer.view.plotting.plotting_widget.\ multi_threaded_plotting_widget import MultiThreadedPlottingWidget +from sohstationviewer.view.db_config.add_edit_single_channel_dialog import \ + AddEditSingleChannelDialog class SOHWidget(MultiThreadedPlottingWidget): @@ -18,6 +21,78 @@ class SOHWidget(MultiThreadedPlottingWidget): def __init__(self, *args, **kwargs): MultiThreadedPlottingWidget.__init__(self, *args, **kwargs) + """ + curr_ax: current axes to be edited + """ + self.curr_ax: Optional[Axes] = None + + def on_button_press_event(self, event): + """ + When right-clicking on a plot with no Keyboard pressed, + set self.curr_ax to the ax of that plot. + """ + modifiers = event.guiEvent.modifiers() + if modifiers != QtCore.Qt.KeyboardModifier.NoModifier: + return super().on_button_press_event(event) + + x = event.xdata + if x is None: + # when clicking outside of the plots + self.curr_ax = None + else: + if event.button == MouseButton.RIGHT: + # RIGHT click + self.curr_ax = event.inaxes + self.parent.raise_() + else: + # LEFT click + self.curr_ax = None + if self.log_idxes is not None: + # For Reftek, need to hightlight the corresponding + # SOH message lines based on the log_idxes of the clicked + # point + self.parent.search_message_dialog.show() + try: + self.parent.search_message_dialog. \ + show_log_entry_from_log_indexes(self.log_idxes) + except ValueError as e: + QtWidgets.QMessageBox.warning(self, "Not found", + str(e)) + else: + self.parent.raise_() + + def contextMenuEvent(self, event): + """ + Create menu showing up when right click mouse to add/edit channel + """ + try: + if '?' in self.curr_ax.chan_db_info['dbChannel']: + warning_action_str = ( + f"Channel '{self.curr_ax.chan_db_info['channel']} '" + "can't be edited because it has '?' in its DB name, " + f"{self.curr_ax.chan_db_info['dbChannel']}.") + + elif 'DEFAULT' in self.curr_ax.chan_db_info['label']: + add_edit_action_str = f"Add new channel {self.curr_ax.chan}" + + else: + add_edit_action_str = f"Edit channel {self.curr_ax.chan}" + except AttributeError: + return + + context_menu = QtWidgets.QMenu(self) + try: + add_edit_chan_action = context_menu.addAction(add_edit_action_str) + add_edit_chan_action.triggered.connect(self.add_edit_channel) + except UnboundLocalError: + pass + try: + context_menu.addAction(warning_action_str) + except UnboundLocalError: + pass + + context_menu.exec_(self.mapToGlobal(event.pos())) + self.curr_ax = None # to make sure curr_ax is clear def init_plot(self, d_obj: GeneralData, data_set_id: Union[str, Tuple[str, str]], @@ -70,13 +145,19 @@ class SOHWidget(MultiThreadedPlottingWidget): data_structure.MD :param chan_id: name of channel """ - if len(c_data['times']) == 0: - return - chan_db_info = c_data['chan_db_info'] - plot_type = chan_db_info['plotType'] - ax = getattr( - self.plotting, plot_functions[plot_type]['plot_function'])( - c_data, chan_db_info, chan_id) + ax = self.plotting.plot_channel(c_data, chan_id, None) c_data['ax'] = ax + ax.c_data = c_data ax.chan = chan_id self.axes.append(ax) + + def add_edit_channel(self): + win = AddEditSingleChannelDialog( + self.parent, + self.plotting, + self.curr_ax.chan, + self.parent.data_type, + self.curr_ax + ) + win.exec() + self.draw() diff --git a/sohstationviewer/view/plotting/waveform_dialog.py b/sohstationviewer/view/plotting/waveform_dialog.py index 16b1763404dc3add874eee871dea6d62d6c49ade..ca0c51ee5b2110da7592947b30d66df5fbdc8880 100755 --- a/sohstationviewer/view/plotting/waveform_dialog.py +++ b/sohstationviewer/view/plotting/waveform_dialog.py @@ -5,7 +5,6 @@ from PySide6 import QtCore, QtWidgets from sohstationviewer.model.general_data.general_data import GeneralData -from sohstationviewer.view.util.plot_func_names import plot_functions from sohstationviewer.view.plotting.plotting_widget.\ multi_threaded_plotting_widget import MultiThreadedPlottingWidget @@ -47,15 +46,9 @@ class WaveformWidget(MultiThreadedPlottingWidget): 'times' and 'data'. Refer to general_data/data_structures.MD :param chan_id: name of channel """ - if len(c_data['times']) == 0: - return - chan_db_info = c_data['chan_db_info'] - plot_type = chan_db_info['plotType'] - - # refer to doc string for mass_pos_data to know the reason for 'ax_wf' - ax = getattr( - self.plotting, plot_functions[plot_type]['plot_function'])( - c_data, chan_db_info, chan_id) + ax = self.plotting.plot_channel(c_data, chan_id, None) + # 'ax_wf' is the ax to plot mass position in WaveformWidget. + # Refer to data_structures.MD for more explanation c_data['ax_wf'] = ax ax.chan = chan_id self.axes.append(ax) diff --git a/tests/database/test_extract_data.py b/tests/database/test_extract_data.py index 2c448b50d5c7779fc616134b763b39913d0aa146..b814084dfa3d46d59fde46595f762ebeac7ba6cc 100644 --- a/tests/database/test_extract_data.py +++ b/tests/database/test_extract_data.py @@ -16,11 +16,14 @@ class TestGetChanPlotInfo(BaseTestCase): Test basic functionality of get_chan_plot_info - channel and data type combination exists in database table `Channels` """ - expected_result = {'channel': 'SOH/Data Def', + expected_result = {'param': 'SOH data definitions', + 'dbChannel': 'SOH/Data Def', + 'channel': 'SOH/Data Def', 'plotType': 'upDownDots', 'height': 2, 'unit': '', 'convertFactor': 1, + 'dbLabel': None, 'label': 'SOH/Data Def', 'fixPoint': 0, 'valueColors': '0:W|1:C'} @@ -29,11 +32,14 @@ class TestGetChanPlotInfo(BaseTestCase): def test_masspos_channel(self): with self.subTest("Mass position 'VM'"): - expected_result = {'channel': 'VM1', + expected_result = {'param': 'Mass position', + 'dbChannel': 'VM?', + 'channel': 'VM1', 'plotType': 'linesMasspos', 'height': 4, 'unit': 'V', 'convertFactor': 0.1, + 'dbLabel': 'MassPos', 'label': 'VM1-MassPos', 'fixPoint': 1, 'valueColors': None} @@ -41,11 +47,14 @@ class TestGetChanPlotInfo(BaseTestCase): expected_result) with self.subTest("Mass position 'MassPos'"): - expected_result = {'channel': 'MassPos1', + expected_result = {'param': 'Mass position', + 'dbChannel': 'MassPos?', + 'channel': 'MassPos1', 'plotType': 'linesMasspos', 'height': 4, 'unit': 'V', 'convertFactor': 1, + 'dbLabel': None, 'label': 'MassPos1', 'fixPoint': 1, 'valueColors': None} @@ -54,26 +63,32 @@ class TestGetChanPlotInfo(BaseTestCase): def test_seismic_channel(self): with self.subTest("RT130 Seismic"): - expected_result = {'channel': 'DS2', + expected_result = {'param': 'Seismic data', + 'dbChannel': 'SEISMIC', + 'channel': 'DS2', 'plotType': 'linesSRate', 'height': 8, 'unit': '', 'convertFactor': 1, - 'label': 'DS2', + 'dbLabel': None, 'fixPoint': 0, - 'valueColors': None} + 'valueColors': None, + 'label': 'DS2'} self.assertDictEqual(get_chan_plot_info('DS2', 'RT130'), expected_result) with self.subTest("MSeed Seismic"): - expected_result = {'channel': 'LHE', + expected_result = {'param': 'Seismic data', + 'dbChannel': 'SEISMIC', + 'channel': 'LHE', 'plotType': 'linesSRate', 'height': 8, 'unit': '', 'convertFactor': 1, - 'label': 'LHE-EW', + 'dbLabel': 'SeismicData', 'fixPoint': 0, - 'valueColors': None} + 'valueColors': None, + 'label': 'LHE-EW'} self.assertDictEqual(get_chan_plot_info('LHE', 'Q330'), expected_result) @@ -83,11 +98,14 @@ class TestGetChanPlotInfo(BaseTestCase): string 'Unknown'. """ # Channel does not exist in database - expected_result = {'channel': 'DEFAULT', + expected_result = {'param': 'Default', + 'dbChannel': 'DEFAULT', + 'channel': 'DEFAULT', 'plotType': 'linesDots', 'height': 2, 'unit': '', 'convertFactor': 1, + 'dbLabel': '', 'label': 'DEFAULT-Bad Channel ID', 'fixPoint': 0, 'valueColors': None} @@ -95,11 +113,14 @@ class TestGetChanPlotInfo(BaseTestCase): expected_result) # Channel exist in database - expected_result = {'channel': 'LCE', + expected_result = {'param': 'Clock phase error', + 'dbChannel': 'LCE', + 'channel': 'LCE', 'plotType': 'linesDots', 'height': 3, 'unit': 'us', 'convertFactor': 1, + 'dbLabel': 'PhaseError', 'label': 'LCE-PhaseError', 'fixPoint': 0, 'valueColors': 'L:W|D:Y'} @@ -114,12 +135,15 @@ class TestGetChanPlotInfo(BaseTestCase): not the string 'Unknown'. """ # noinspection PyDictCreation - expected_result = {'channel': 'DEFAULT', + expected_result = {'param': 'Default', + 'dbChannel': 'DEFAULT', + 'channel': 'DEFAULT', 'plotType': 'linesDots', 'height': 2, 'unit': '', 'convertFactor': 1, - 'label': None, # Change for each test case + 'dbLabel': '', + 'label': 'DEFAULT-SOH/Data Def', 'fixPoint': 0, 'valueColors': None} diff --git a/tests/model/general_data/test_general_data_helper.py b/tests/model/general_data/test_general_data_helper.py index 7e575be04f2ac0af6c146aa6b560161005375c5f..849a7ad9318ea5b450a8de502bdaf918076e602c 100644 --- a/tests/model/general_data/test_general_data_helper.py +++ b/tests/model/general_data/test_general_data_helper.py @@ -1,11 +1,9 @@ -import numpy as np from pathlib import Path -from unittest.mock import patch from sohstationviewer.model.general_data.general_data_helper import ( _check_related_gaps, squash_gaps, sort_data, retrieve_data_time_from_data_dict, retrieve_gaps_from_data_dict, - combine_data, apply_convert_factor_to_data_dict, read_text + combine_data, read_text ) from tests.base_test_case import BaseTestCase @@ -302,22 +300,3 @@ class TestCombineData(BaseTestCase): self.assertListEqual( data_dict['STA1']['CH1']['tracesInfo'][0]['times'].tolist(), [5, 8, 11, 15, 25, 29, 33, 36, 40]) - - -class TestApplyConvertFactorToDataDict(BaseTestCase): - def setUp(self) -> None: - self.data_dict = { - 'STA1': { - 'CH1': {'tracesInfo': [{'data': np.array([1, 2, 2, -1])}]} - } - } - self.expected_data = [0.1, 0.2, 0.2, -0.1] - - @patch('sohstationviewer.model.general_data.general_data_helper.' - 'get_convert_factor') - def test_convert_factor(self, mock_get_convert_factor): - mock_get_convert_factor.return_value = 0.1 - apply_convert_factor_to_data_dict('STA1', self.data_dict, 'Q330') - self.assertEqual( - self.data_dict['STA1']['CH1']['tracesInfo'][0]['data'].tolist(), - self.expected_data) diff --git a/tests/view/plotting/plotting_widget/test_plotting_helper.py b/tests/view/plotting/plotting_widget/test_plotting_helper.py index a83c52803b59087656977ab286eda571c8088085..ddf6d12b9e1d1ce6cf77ddbc8fcdfcfe12b523c7 100644 --- a/tests/view/plotting/plotting_widget/test_plotting_helper.py +++ b/tests/view/plotting/plotting_widget/test_plotting_helper.py @@ -1,8 +1,13 @@ +import numpy as np +from numpy import testing as nptesting +from unittest.mock import patch + from sohstationviewer.view.plotting.plotting_widget.plotting_helper import ( get_masspos_value_colors, get_categorized_data_from_value_color_equal_on_upper_bound, get_categorized_data_from_value_color_equal_on_lower_bound, - get_colors_sizes_for_abs_y_from_value_colors + get_colors_sizes_for_abs_y_from_value_colors, + apply_convert_factor ) from sohstationviewer.view.util.color import clr from tests.base_test_case import BaseTestCase @@ -163,3 +168,14 @@ class TestGetColorsSizesForAbsYFromValueColors(BaseTestCase): ] ) self.assertEqual(sizes, [1.5] * len(y)) + + +class TestApplyConvertFactor(BaseTestCase): + @patch('sohstationviewer.view.plotting.plotting_widget.plotting_helper.' + 'get_convert_factor') + def test_convert_factor(self, mock_get_convert_factor): + mock_get_convert_factor.return_value = 0.1 + test_data = [np.array([1, 2, 2, -1])] + expected_data = [np.array([0.1, 0.2, 0.2, -0.1])] + result_data = apply_convert_factor(test_data, 'CHA', 'Q330') + nptesting.assert_array_equal(result_data, expected_data)