Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • software_public/passoft/sohstationviewer
1 result
Show changes
Commits on Source (18)
Showing
with 372 additions and 105 deletions
......@@ -7,6 +7,7 @@ ROOT_PATH = Path(os.path.abspath(__file__)).parent.parent
# The current version of SOHStationViewer
SOFTWARE_VERSION = '2024.2.1.0'
BUILD_TIME = "March 13, 2024"
# waveform pattern
WF_1ST = 'A-HLM-V'
......
......@@ -245,23 +245,6 @@ def add_thousand_separator(value: float) -> str:
return new_value
def apply_convert_factor(c_data: dict, convert_factor: float):
"""
convertFactor = 150mV/count = 150V/1000count
=> unit data * convertFactor= data *150/1000 V
:param c_data: data of the channel which includes down-sampled
data in keys 'times' and 'data'.
Refer to data_dict in data_structures.MD
:param convert_factor: convertFactor field retrieved from
db table Channels for this channel
"""
if c_data['needConvert']:
c_data['needConvert'] = False
if convert_factor is not None and convert_factor != 1:
c_data['data'] = [d * convert_factor for d in c_data['data']]
def is_hex(text: str) -> bool:
"""
Check if text is hexadecimal
......
No preview for this file type
# SOH Station Viewer Documentation
# SOHViewer Documentation
Welcome to the SOH Station Viewer documentation. Here you will find usage guides and other useful information in navigating and using this software.
Welcome to the SOHViewer documentation. Here you will find usage guides and other useful information in navigating and using this software.
On the left-hand side you will find a list of currently available help topics.
......
......@@ -12,6 +12,7 @@ from sohstationviewer.conf.config_processor import (
ConfigProcessor,
BadConfigError,
)
from sohstationviewer.conf import constants
def fix_relative_paths() -> None:
......@@ -73,7 +74,7 @@ def check_if_user_want_to_reset_config() -> bool:
def main():
# Change the working directory so that relative paths work correctly.
fix_relative_paths()
print(f"SOHViewer - Version {constants.SOFTWARE_VERSION}")
app = QtWidgets.QApplication(sys.argv)
wnd = MainWindow()
......
......@@ -26,7 +26,7 @@ Note: log_data for RT130's dataset has only one channel: SOH
'chan_db_info' (dict): the plotting parameters got from database
for this channel - dict,
'ax': axes to draw the channel in PlottingWidget
'ax_wf' (matplotlib.axes.Axes): axes to draw the channel in WaveformWidget
'ax_wf': axes to draw the channel in WaveformWidget
'visible': flag to show or hide channel
}
}
......
from __future__ import annotations
from typing import Optional, List
from typing import List
"""
Routines building upon obspy.io.reftek.packet.
......@@ -302,9 +302,7 @@ class SOHPacket(obspy_rt130_packet.Packet):
raise NotImplementedError(msg.format(packet_type))
@staticmethod
def time_tag(time: UTCDateTime, implement_time: Optional[int] = None):
if implement_time is not None and time > UTCDateTime(ns=implement_time): # noqa: E501
time = UTCDateTime(ns=implement_time)
def time_tag(time: UTCDateTime):
return "{:04d}:{:03d}:{:02d}:{:02d}:{:02d}:{:03d}".format(time.year,
time.julday,
time.hour,
......@@ -391,6 +389,10 @@ class SCPacket(SOHPacket):
.format(self.time_tag(self.time),
self.unit_id.decode()))
info.append(packet_soh_string)
implement_time_tag = self.time_tag(
UTCDateTime(self.implement_time / 10 ** 9)
)
info.append("\n Implemented = " + implement_time_tag)
info.append("\n Experiment Number = " + self.experiment_number_sc)
info.append("\n Experiment Name = " + self.experiment_name)
info.append("\n Comments - " + self.experiment_comment)
......@@ -477,9 +479,13 @@ class OMPacket(SOHPacket):
info = []
# info.append(self.packet_tagline)
packet_soh_string = ("\nOperating Mode Definition {:s} ST: {:s}"
.format(self.time_tag(self.time, implement_time=self.implement_time), # noqa: E501
.format(self.time_tag(self.time), # noqa: E501
self.unit_id.decode()))
info.append(packet_soh_string)
implement_time_tag = self.time_tag(
UTCDateTime(self.implement_time / 10 ** 9)
)
info.append("\n Implemented = " + implement_time_tag)
info.append("\n Operating Mode 72A Power State " + self._72A_power_state) # noqa: E501
info.append("\n Operating Mode Recording Mode " + self.recording_mode)
info.append("\n Operating Mode Auto Dump on ET " + self.auto_dump_on_ET) # noqa: E501
......@@ -557,9 +563,13 @@ class DSPacket(SOHPacket):
info = []
info.append(self.packet_tagline)
packet_soh_string = ("\nData Stream Definition {:s} ST: {:s}"
.format(self.time_tag(self.time, implement_time=self.implement_time), # noqa: E501
.format(self.time_tag(self.time), # noqa: E501
self.unit_id.decode()))
info.append(packet_soh_string)
implement_time_tag = self.time_tag(
UTCDateTime(self.implement_time / 10 ** 9)
)
info.append("\n Implemented = " + implement_time_tag)
for ind_ds in range(1, DS_MAX_NBR_ST + 1):
stream_number = getattr(self, "ds" + str(ind_ds) + "_number")
if stream_number.strip():
......@@ -640,9 +650,13 @@ class ADPacket(SOHPacket):
info = []
# info.append(self.packet_tagline)
packet_soh_string = ("\nAuxiliary Data Parameter {:s} ST: {:s}"
.format(self.time_tag(self.time, implement_time=self.implement_time), # noqa: E501
.format(self.time_tag(self.time), # noqa: E501
self.unit_id.decode()))
info.append(packet_soh_string)
implement_time_tag = self.time_tag(
UTCDateTime(self.implement_time / 10 ** 9)
)
info.append("\n Implemented = " + implement_time_tag)
channel_nbr = [str(ind_chan) for ind_chan, val in
enumerate(self.channels, 1) if val.strip()]
info.append("\n Channels " + ", ".join(channel_nbr))
......@@ -732,9 +746,13 @@ class CDPacket(SOHPacket):
info = []
# info.append(self.packet_tagline)
packet_soh_string = ("\nCalibration Definition {:s} ST: {:s}"
.format(self.time_tag(self.time, implement_time=self.implement_time), # noqa: E501
.format(self.time_tag(self.time), # noqa: E501
self.unit_id.decode()))
info.append(packet_soh_string)
implement_time_tag = self.time_tag(
UTCDateTime(self.implement_time / 10 ** 9)
)
info.append("\n Implemented = " + implement_time_tag)
if self._72A_start_time.split():
info.append("\n 72A Calibration Start Time " + self._72A_start_time) # noqa: E501
......@@ -855,9 +873,13 @@ class FDPacket(SOHPacket):
info = []
# info.append(self.packet_tagline)
packet_soh_string = ("\nFilter Description {:s} ST: {:s}"
.format(self.time_tag(self.time, implement_time=self.implement_time), # noqa: E501
.format(self.time_tag(self.time), # noqa: E501
self.unit_id.decode()))
info.append(packet_soh_string)
implement_time_tag = self.time_tag(
UTCDateTime(self.implement_time / 10 ** 9)
)
info.append("\n Implemented = " + implement_time_tag)
for ind_fb in range(1, self.nbr_fbs + 1):
info.append("\n Filter Block Count " + str(getattr(self, "fb" + str(ind_fb) + "_filter_block_count"))) # noqa: E501
info.append("\n Filter ID " + getattr(self, "fb" + str(ind_fb) + "_filter_ID")) # noqa: E501
......
import sys
from typing import Union
from PySide6 import QtWidgets, QtCore
from PySide6.QtWidgets import QApplication, QWidget, QDialog, QLabel, QFrame
from sohstationviewer.conf import constants
def add_separation_line(layout):
"""
Add a line for separation to the given layout.
:param layout: QLayout - the layout that contains the line
"""
label = QLabel()
label.setFrameStyle(QFrame.Shape.HLine | QFrame.Shadow.Sunken)
label.setLineWidth(1)
layout.addWidget(label)
class AboutDialog(QDialog):
"""
Dialog to show information of the software.
About Dialog is always opened and will be raised up when the about menu
action is triggered.
"""
def __init__(self, parent: Union[QWidget, QApplication]):
"""
:param parent: the parent widget
"""
super(AboutDialog, self).__init__(parent)
# set block interaction with other windows
self.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal)
self.software_name_label = QLabel("SOHViewer")
self.software_name_label.setStyleSheet(
"QLabel {font-size: 18pt; font-style: bold; color: darkblue}"
)
description = ("Visualize State-of-Health packets from data in "
"mseed or reftek formats recorded\n"
"by different types of data loggers.")
self.description_label = QLabel(description)
version = f"Version {constants.SOFTWARE_VERSION}"
self.version_label = QLabel(version)
built_time = f"Built on {constants.BUILD_TIME}"
self.built_time_label = QLabel(built_time)
copyright = u"Copyright \u00A9 2024 EarthScope Consortium"
self.copyright_label = QLabel(copyright)
self.ok_button = QtWidgets.QPushButton('OK', self)
self.setup_ui()
self.connect_signals()
def setup_ui(self):
self.setWindowTitle("About SOHViewer")
main_layout = QtWidgets.QVBoxLayout()
self.setLayout(main_layout)
main_layout.addWidget(self.software_name_label)
main_layout.addWidget(self.description_label)
add_separation_line(main_layout)
main_layout.addWidget(self.version_label)
main_layout.addWidget(self.built_time_label)
main_layout.addWidget(self.copyright_label)
button_layout = QtWidgets.QHBoxLayout()
main_layout.addLayout(button_layout)
button_layout.addStretch()
button_layout.addWidget(self.ok_button)
def connect_signals(self) -> None:
self.ok_button.clicked.connect(self.close)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
test = AboutDialog(None)
test.exec()
sys.exit(app.exec())
from typing import Dict, List, Union, Optional, Tuple
from pathlib import Path
from PySide6 import QtWidgets, QtCore
from PySide6 import QtWidgets, QtCore, QtGui
from PySide6.QtWidgets import QDialogButtonBox, QDialog, QPlainTextEdit, \
QMainWindow
......@@ -328,12 +328,51 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
count, COL['dataType']).setCurrentText(r['dataType'])
self.soh_list_table_widget.item(
count, COL['preferredSOHs']).setText(r['preferredSOHs'])
if r['default'] == 1:
self.set_default_row(count)
if r['current'] == 1:
self.curr_sel_changed(count)
self.soh_list_table_widget.selectRow(count)
count += 1
self.update()
def set_default_row(self, row_idx):
"""
Set row at row_idx to default in which,
+ Edit and clear button are disabled and greyed out.
+ All other widgets except for the select radio button are disabled
but not greyed out.
"""
self.soh_list_table_widget.item(
row_idx, COL['name']).setFlags(
QtCore.Qt.ItemFlag.ItemIsSelectable |
QtCore.Qt.ItemFlag.ItemIsEnabled)
data_type_widget = self.soh_list_table_widget.cellWidget(
row_idx, COL['dataType'])
data_type_widget.setEnabled(False)
# Only want it to be unchangeable but still look active so
# the text color is changed to black instead of being grey as default
# for disable column box.
palette = data_type_widget.palette()
palette.setColor(QtGui.QPalette.ColorRole.Text, 'black')
palette.setColor(QtGui.QPalette.ColorRole.ButtonText, 'black')
data_type_widget.setPalette(palette)
self.soh_list_table_widget.item(
row_idx, COL['preferredSOHs']).setFlags(
QtCore.Qt.ItemFlag.ItemIsSelectable |
QtCore.Qt.ItemFlag.ItemIsEnabled)
# Buttons Edit and Clear are disable showing that this row is a default
# row and can't be editable.
self.soh_list_table_widget.cellWidget(
row_idx, COL['edit']).setEnabled(False)
self.soh_list_table_widget.cellWidget(
row_idx, COL['clr']).setEnabled(False)
def get_row(self, row_idx):
"""
Get content of a self.soh_list_table_widget's row
......@@ -744,6 +783,6 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
:return id_rows: [dict,] - list of data for each row
"""
id_rows = execute_db_dict(
"SELECT name, preferredSOHs, dataType, current FROM ChannelPrefer "
"SELECT * FROM ChannelPrefer "
" ORDER BY name ASC")
return id_rows
......@@ -242,8 +242,7 @@ class AddEditSingleChannelDialog(QDialog):
)
PlottingAxes.clean_axes(self.ax)
self.plotting.plot_channel(self.ax.c_data,
self.channel_name_lnedit.text(),
self.ax)
self.channel_name_lnedit.text())
self.close()
def set_buttons_enabled(self):
......
import getpass
import os
from PySide6 import QtCore, QtWidgets
UrlRole = QtCore.Qt.UserRole + 1
EnabledRole = QtCore.Qt.UserRole + 2
EXT_DRIVE_BASE = ['/media', '/run/media']
def get_external_drive_url():
"""
External drives of Linux machine can be located under:
/media/<username>
OR /run/media/<username>
return url_dict: dict with key is a url of an external drive's path and
value is the drive's name.
"""
url_dict = {}
for base in EXT_DRIVE_BASE:
if not os.path.isdir(base):
continue
username = getpass.getuser()
external_drive_root = os.path.join(base, username)
if not os.path.isdir(external_drive_root):
continue
for d in os.listdir(external_drive_root):
full_path = os.path.join(os.path.join(external_drive_root, d))
if not os.path.isdir(full_path):
continue
url_dict[QtCore.QUrl.fromLocalFile(full_path)] = d
return url_dict
class URLShortcutTextDelegate(QtWidgets.QStyledItemDelegate):
"""
Help with displaying shortcut text for each drive.
https://stackoverflow.com/questions/69284292/is-there-an-option-to-rename-a-qurl-shortcut-in-a-sidebar-of-a-qfiledialog # noqa: E501
"""
mapping = dict()
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
url = index.data(UrlRole)
text = self.mapping.get(url)
if isinstance(text, str):
option.text = text
is_enabled = index.data(EnabledRole)
if is_enabled is not None and not is_enabled:
option.state &= ~QtWidgets.QStyle.State_Enabled
class FileDialogForLinuxExternalDrive(QtWidgets.QFileDialog):
"""
File Dialog that show external drives of linux (Ubuntu, Fedora) on sidebar
"""
def __init__(self, parent, curr_dir):
url_dict = get_external_drive_url()
# only use customized sidebar if len(url_dict)>0
options = (QtWidgets.QFileDialog.Option.DontUseNativeDialog if url_dict
else QtWidgets.QFileDialog.Option.ShowDirsOnly)
super().__init__(parent, caption="Select Main Data Directory",
options=options,
fileMode=QtWidgets.QFileDialog.FileMode.Directory)
if url_dict:
self.setSidebarUrls(self.sidebarUrls() + list(url_dict.keys()))
sidebar = self.findChild(QtWidgets.QListView, "sidebar")
# fit sidebar with content
sidebar.setMinimumWidth(sidebar.sizeHintForColumn(0))
delegate = URLShortcutTextDelegate(sidebar)
delegate.mapping = url_dict
sidebar.setItemDelegate(delegate)
self.setDirectory(curr_dir)
......@@ -15,6 +15,7 @@ from sohstationviewer.model.data_loader import DataLoader
from sohstationviewer.model.general_data.general_data import \
GeneralData
from sohstationviewer.view.about_dialog import AboutDialog
from sohstationviewer.view.calendar.calendar_dialog import CalendarDialog
from sohstationviewer.view.db_config.channel_dialog import ChannelDialog
from sohstationviewer.view.db_config.data_type_dialog import DataTypeDialog
......@@ -22,7 +23,9 @@ from sohstationviewer.view.db_config.param_dialog import ParamDialog
from sohstationviewer.view.db_config.plot_type_dialog import PlotTypeDialog
from sohstationviewer.view.file_information.get_file_information import \
extract_data_set_info
from sohstationviewer.view.file_list_widget import FileListItem
from sohstationviewer.view.file_list.file_dialog_for_linux_external_drive \
import FileDialogForLinuxExternalDrive
from sohstationviewer.view.file_list.file_list_widget import FileListItem
from sohstationviewer.view.plotting.gps_plot.extract_gps_data import \
extract_gps_data
from sohstationviewer.view.plotting.gps_plot.gps_dialog import GPSDialog
......@@ -192,6 +195,10 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
"""
self.help_browser: HelpBrowser = HelpBrowser()
"""
about_dialog: Dialog showing info of the app.
"""
self.about_dialog: AboutDialog = AboutDialog(self)
"""
search_message_dialog: Display log, soh message with searching feature.
"""
self.search_message_dialog: SearchMessageDialog = SearchMessageDialog()
......@@ -214,6 +221,14 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
self.yyyy_mm_dd_action.trigger()
@QtCore.Slot()
def open_about(self):
"""
About dialog is always open. This function will raise it up when called
"""
self.about_dialog.show()
self.about_dialog.raise_()
@QtCore.Slot()
def save_plot(self):
self.plotting_widget.save_plot('SOH-Plot')
......@@ -420,9 +435,8 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
the user can load data. The starting directory is taken from
curr_dir_line_edit.
"""
fd = QtWidgets.QFileDialog(self)
fd.setFileMode(QtWidgets.QFileDialog.FileMode.Directory)
fd.setDirectory(self.curr_dir_line_edit.text())
fd = FileDialogForLinuxExternalDrive(
self, self.curr_dir_line_edit.text())
fd.exec()
if fd.result() == QtWidgets.QDialog.DialogCode.Accepted:
new_path = fd.selectedFiles()[0]
......@@ -623,6 +637,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
Read data from selected files/directories, process and plot channels
read from those according to current options set on the GUI
"""
self.replot_button.setEnabled(False)
self.plot_diff_data_set_id_button.setEnabled(False)
display_tracking_info(self.tracking_info_text_browser,
"Loading started",
......@@ -891,6 +906,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
Plot using data from self.data_object with the current options set
from GUI
"""
self.replot_button.setEnabled(False)
self.clear_plots()
if self.has_problem:
return
......
......@@ -179,6 +179,34 @@ class MultiThreadedPlottingWidget(PlottingWidget):
self.plotting_axes.set_title(self.title)
self.plotting_axes.add_gap_bar(self.gaps)
def create_ax(self, plotting_data, chan_id):
"""
Create axes for chan_id in plotting_data to add to key ax or ax_wf of
channel, and keep track of axes themselves in self.axes.
:param plotting_data: dict of channels data by chan_id
:param chan_id: name of channel
"""
if ('LINES' in
plotting_data[chan_id]['chan_db_info']['plotType'].upper()):
has_min_max_lines = True
else:
has_min_max_lines = False
plot_h = self.plotting_axes.get_height(
plotting_data[chan_id]['chan_db_info']['height'])
ax = self.plotting_axes.create_axes(
self.plotting_bot, plot_h, has_min_max_lines)
if self.name == 'SOH':
plotting_data[chan_id]['ax'] = ax
else:
plotting_data[chan_id]['ax_wf'] = ax
ax.c_data = plotting_data[chan_id]
ax.chan = chan_id
self.axes.append(ax)
def create_plotting_channel_processors(
self, plotting_data: Dict, chan_order) -> None:
"""
......@@ -193,6 +221,10 @@ class MultiThreadedPlottingWidget(PlottingWidget):
continue
if not plotting_data[chan_id].get('visible'):
continue
# create ax before plotting, or it will be disordered
# because of threading
self.create_ax(plotting_data, chan_id)
channel_processor = PlottingChannelProcessor(
plotting_data[chan_id], chan_id,
self.min_x, self.max_x
......@@ -276,7 +308,7 @@ class MultiThreadedPlottingWidget(PlottingWidget):
if channel_id is not None:
self.data_processors.pop(0)
self.notification.emit(f'Plotting channel {channel_id}...')
self.plot_single_channel(channel_data, channel_id)
self.plotting.plot_channel(channel_data, channel_id)
self.draw()
try:
......@@ -314,8 +346,8 @@ class MultiThreadedPlottingWidget(PlottingWidget):
finished_msg = f'{self.name} plot finished.'
display_tracking_info(self.tracking_box, finished_msg, LogType.INFO)
self.is_working = False
self.handle_replot_button()
def done(self):
"""
......
......@@ -69,11 +69,6 @@ class Plotting:
otherwise, plot_from_value_color_equal_on_lower_bound will be use
:return: ax in which the channel is plotted
"""
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(
......@@ -146,12 +141,6 @@ class Plotting:
:param ax: axes to plot channel
:return ax: axes of the channel
"""
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('|')
sample_no_colors = []
......@@ -214,12 +203,6 @@ class Plotting:
:param ax: axes to plot channel
:return ax: axes of the channel
"""
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
points_list = [[], []]
......@@ -283,11 +266,6 @@ class Plotting:
:param ax: axes to plot channel
:return ax: axes of the channel
"""
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)
# Set the color to white by default
color = '#000000'
if chan_db_info['valueColors'] not in [None, 'None', '']:
......@@ -337,11 +315,6 @@ class Plotting:
main-title
:return ax: axes of the channel
"""
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)
......@@ -472,11 +445,12 @@ class Plotting:
if value_colors is None:
return
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
)
total_x = sum([len(x) for x in x_list])
self.plotting_axes.set_axes_info(
......@@ -500,8 +474,7 @@ class Plotting:
# 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 = apply_convert_factor(
y_list, chan_id, self.main_window.data_type)[0]
ax.y_center = y_list[0]
ax.chan_db_info = chan_db_info
return ax
......@@ -533,26 +506,26 @@ class Plotting:
ax.chan_db_info = chan_db_info
return ax
def plot_channel(self, c_data: Dict, chan_id: str,
ax: Optional[Axes]) -> Axes:
def plot_channel(self, c_data: Dict, chan_id: str) -> None:
"""
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
if self.parent.name == 'SOH':
ax = c_data['ax']
else:
ax = c_data['ax_wf']
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(
getattr(
self, plot_types[plot_type]['plot_function'])(
c_data, chan_db_info, chan_id, ax)
return ax
......@@ -192,7 +192,7 @@ def get_colors_sizes_for_abs_y_from_value_colors(
def apply_convert_factor(data: List[np.ndarray], chan_id: str, data_type: str
) -> np.ndarray:
) -> List[np.ndarray]:
"""
Convert data according to convert_factor got from DB
......
......@@ -407,6 +407,10 @@ class PlottingWidget(QtWidgets.QScrollArea):
+ If the chan_data has key 'logIdx', raise the Search Messages dialog,
focus SOH tab, roll to the corresponding line.
"""
if event.mouseevent.name == 'scroll_event':
return
if event.mouseevent.button in ('up', 'down'):
return
self.is_button_press_event_triggered_pick_event = True
artist = event.artist
self.log_idxes = None
......@@ -507,7 +511,11 @@ class PlottingWidget(QtWidgets.QScrollArea):
self.ruler_text = None
except AttributeError:
pass
if (self.main_window.tps_check_box.isChecked() and
modifiers in [QtCore.Qt.KeyboardModifier.ControlModifier,
QtCore.Qt.KeyboardModifier.MetaModifier,
QtCore.Qt.KeyboardModifier.NoModifier]):
self.main_window.tps_dlg.set_indexes_and_display_info(xdata)
for w in self.peer_plotting_widgets:
if not w.has_data:
continue
......@@ -840,3 +848,16 @@ class PlottingWidget(QtWidgets.QScrollArea):
self.draw()
except AttributeError:
pass
def handle_replot_button(self):
"""
Check if all plotting_widgets are done with plotting before enable
replot buttons.
"""
any_is_working = False
for w in self.peer_plotting_widgets:
if w.is_working:
any_is_working = True
break
if not any_is_working:
self.main_window.replot_button.setEnabled(True)
# Drawing State-Of-Health channels and mass position
from typing import Tuple, Union, Dict, Optional
from typing import Tuple, Union, Optional
from matplotlib.axes import Axes
from matplotlib.backend_bases import MouseButton
from PySide6 import QtWidgets, QtCore
......@@ -144,21 +144,6 @@ class SOHWidget(MultiThreadedPlottingWidget):
self.processing_log.append((msg, LogType.WARNING))
return True
def plot_single_channel(self, c_data: Dict, chan_id: str):
"""
Plot the channel chan_id.
:param c_data: data of the channel which includes down-sampled
data in keys 'times' and 'data'. Refer to data_dict in
data_structure.MD
:param chan_id: name of channel
"""
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,
......
# Display time-power-squared values for waveform data
from math import sqrt
from typing import Union, Tuple, Dict, List
from PySide6 import QtWidgets, QtCore
......@@ -6,17 +7,23 @@ from PySide6.QtCore import QEventLoop, Qt, QSize
from PySide6.QtGui import QCursor
from PySide6.QtWidgets import QApplication, QTabWidget
from sohstationviewer.database.extract_data import (
from sohstationviewer.conf.constants import DAY_LIMIT_FOR_TPS_IN_ONE_TAB
from sohstationviewer.controller.util import \
display_tracking_info, add_thousand_separator
from sohstationviewer.controller.plotting_data import format_time
from sohstationviewer.database.extract_data import \
get_color_def, get_color_ranges
)
from sohstationviewer.model.general_data.general_data import GeneralData
from sohstationviewer.view.plotting.time_power_square.\
time_power_squared_widget import TimePowerSquaredWidget
from sohstationviewer.controller.util import display_tracking_info
from sohstationviewer.model.general_data.general_data import GeneralData
from sohstationviewer.view.plotting.time_power_square.\
time_power_squared_helper import get_start_5mins_of_diff_days
from sohstationviewer.conf.constants import DAY_LIMIT_FOR_TPS_IN_ONE_TAB
time_power_squared_helper import \
get_start_5mins_of_diff_days, find_tps_tm_idx
class TimePowerSquaredDialog(QtWidgets.QWidget):
......@@ -317,7 +324,7 @@ class TimePowerSquaredDialog(QtWidgets.QWidget):
else:
self.main_window.tps_tab_total = len(
d_obj.waveform_data[data_set_id])
for chan_id in d_obj.waveform_data[data_set_id]:
for chan_id in sorted(d_obj.waveform_data[data_set_id].keys()):
self.create_tps_widget(
data_set_id, chan_id,
{chan_id: d_obj.waveform_data[data_set_id][chan_id]})
......@@ -346,3 +353,30 @@ class TimePowerSquaredDialog(QtWidgets.QWidget):
tps_widget.plot_channels(
data_dict, data_set_id, self.start_5mins_of_diff_days,
self.min_x, self.max_x)
def set_indexes_and_display_info(self, timestamp):
"""
computing five_minute_idx/day_idx and displaying info of all tps
channels' points.
:param timestamp: real timestamp value
"""
# calculate the indexes corresponding to the timestamp
self.five_minute_idx, self.day_idx = find_tps_tm_idx(
timestamp, self.start_5mins_of_diff_days)
# timestamp in display format
format_t = format_time(timestamp, self.date_format, 'HH:MM:SS')
# display the highlighted point's info on all tabs
info_str = f"<pre>{format_t}:"
for tps_widget in self.tps_widget_dict.values():
for chan_id in tps_widget.plotting_data1:
c_data = tps_widget.plotting_data1[chan_id]
data = c_data['tps_data'][self.day_idx,
self.five_minute_idx]
info_str += (f" {chan_id}:"
f"{add_thousand_separator(sqrt(data))}")
info_str += " (counts)</pre>"
display_tracking_info(self.info_text_browser, info_str)