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 (16)
Showing
with 382 additions and 297 deletions
...@@ -7,6 +7,7 @@ ROOT_PATH = Path(os.path.abspath(__file__)).parent.parent ...@@ -7,6 +7,7 @@ ROOT_PATH = Path(os.path.abspath(__file__)).parent.parent
# The current version of SOHStationViewer # The current version of SOHStationViewer
SOFTWARE_VERSION = '2024.2.1.0' SOFTWARE_VERSION = '2024.2.1.0'
BUILD_TIME = "March 13, 2024"
# waveform pattern # waveform pattern
WF_1ST = 'A-HLM-V' WF_1ST = 'A-HLM-V'
......
...@@ -245,23 +245,6 @@ def add_thousand_separator(value: float) -> str: ...@@ -245,23 +245,6 @@ def add_thousand_separator(value: float) -> str:
return new_value 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: def is_hex(text: str) -> bool:
""" """
Check if text is hexadecimal 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. 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 ( ...@@ -12,6 +12,7 @@ from sohstationviewer.conf.config_processor import (
ConfigProcessor, ConfigProcessor,
BadConfigError, BadConfigError,
) )
from sohstationviewer.conf import constants
def fix_relative_paths() -> None: def fix_relative_paths() -> None:
...@@ -73,7 +74,7 @@ def check_if_user_want_to_reset_config() -> bool: ...@@ -73,7 +74,7 @@ def check_if_user_want_to_reset_config() -> bool:
def main(): def main():
# Change the working directory so that relative paths work correctly. # Change the working directory so that relative paths work correctly.
fix_relative_paths() fix_relative_paths()
print(f"SOHViewer - Version {constants.SOFTWARE_VERSION}")
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
wnd = MainWindow() wnd = MainWindow()
......
from __future__ import annotations from __future__ import annotations
from typing import Optional, List from typing import List
""" """
Routines building upon obspy.io.reftek.packet. Routines building upon obspy.io.reftek.packet.
...@@ -302,9 +302,7 @@ class SOHPacket(obspy_rt130_packet.Packet): ...@@ -302,9 +302,7 @@ class SOHPacket(obspy_rt130_packet.Packet):
raise NotImplementedError(msg.format(packet_type)) raise NotImplementedError(msg.format(packet_type))
@staticmethod @staticmethod
def time_tag(time: UTCDateTime, implement_time: Optional[int] = None): def time_tag(time: UTCDateTime):
if implement_time is not None and time > UTCDateTime(ns=implement_time): # noqa: E501
time = UTCDateTime(ns=implement_time)
return "{:04d}:{:03d}:{:02d}:{:02d}:{:02d}:{:03d}".format(time.year, return "{:04d}:{:03d}:{:02d}:{:02d}:{:02d}:{:03d}".format(time.year,
time.julday, time.julday,
time.hour, time.hour,
...@@ -391,6 +389,10 @@ class SCPacket(SOHPacket): ...@@ -391,6 +389,10 @@ class SCPacket(SOHPacket):
.format(self.time_tag(self.time), .format(self.time_tag(self.time),
self.unit_id.decode())) self.unit_id.decode()))
info.append(packet_soh_string) 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 Number = " + self.experiment_number_sc)
info.append("\n Experiment Name = " + self.experiment_name) info.append("\n Experiment Name = " + self.experiment_name)
info.append("\n Comments - " + self.experiment_comment) info.append("\n Comments - " + self.experiment_comment)
...@@ -477,9 +479,13 @@ class OMPacket(SOHPacket): ...@@ -477,9 +479,13 @@ class OMPacket(SOHPacket):
info = [] info = []
# info.append(self.packet_tagline) # info.append(self.packet_tagline)
packet_soh_string = ("\nOperating Mode Definition {:s} ST: {:s}" 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())) self.unit_id.decode()))
info.append(packet_soh_string) 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 72A Power State " + self._72A_power_state) # noqa: E501
info.append("\n Operating Mode Recording Mode " + self.recording_mode) 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 info.append("\n Operating Mode Auto Dump on ET " + self.auto_dump_on_ET) # noqa: E501
...@@ -557,9 +563,13 @@ class DSPacket(SOHPacket): ...@@ -557,9 +563,13 @@ class DSPacket(SOHPacket):
info = [] info = []
info.append(self.packet_tagline) info.append(self.packet_tagline)
packet_soh_string = ("\nData Stream Definition {:s} ST: {:s}" 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())) self.unit_id.decode()))
info.append(packet_soh_string) 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): for ind_ds in range(1, DS_MAX_NBR_ST + 1):
stream_number = getattr(self, "ds" + str(ind_ds) + "_number") stream_number = getattr(self, "ds" + str(ind_ds) + "_number")
if stream_number.strip(): if stream_number.strip():
...@@ -640,9 +650,13 @@ class ADPacket(SOHPacket): ...@@ -640,9 +650,13 @@ class ADPacket(SOHPacket):
info = [] info = []
# info.append(self.packet_tagline) # info.append(self.packet_tagline)
packet_soh_string = ("\nAuxiliary Data Parameter {:s} ST: {:s}" 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())) self.unit_id.decode()))
info.append(packet_soh_string) 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 channel_nbr = [str(ind_chan) for ind_chan, val in
enumerate(self.channels, 1) if val.strip()] enumerate(self.channels, 1) if val.strip()]
info.append("\n Channels " + ", ".join(channel_nbr)) info.append("\n Channels " + ", ".join(channel_nbr))
...@@ -732,9 +746,13 @@ class CDPacket(SOHPacket): ...@@ -732,9 +746,13 @@ class CDPacket(SOHPacket):
info = [] info = []
# info.append(self.packet_tagline) # info.append(self.packet_tagline)
packet_soh_string = ("\nCalibration Definition {:s} ST: {:s}" 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())) self.unit_id.decode()))
info.append(packet_soh_string) 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(): if self._72A_start_time.split():
info.append("\n 72A Calibration Start Time " + self._72A_start_time) # noqa: E501 info.append("\n 72A Calibration Start Time " + self._72A_start_time) # noqa: E501
...@@ -855,9 +873,13 @@ class FDPacket(SOHPacket): ...@@ -855,9 +873,13 @@ class FDPacket(SOHPacket):
info = [] info = []
# info.append(self.packet_tagline) # info.append(self.packet_tagline)
packet_soh_string = ("\nFilter Description {:s} ST: {:s}" 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())) self.unit_id.decode()))
info.append(packet_soh_string) 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): 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 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 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 typing import Dict, List, Union, Optional, Tuple
from pathlib import Path from pathlib import Path
from PySide6 import QtWidgets, QtCore from PySide6 import QtWidgets, QtCore, QtGui
from PySide6.QtWidgets import QDialogButtonBox, QDialog, QPlainTextEdit, \ from PySide6.QtWidgets import QDialogButtonBox, QDialog, QPlainTextEdit, \
QMainWindow QMainWindow
...@@ -328,12 +328,51 @@ class ChannelPreferDialog(OneWindowAtATimeDialog): ...@@ -328,12 +328,51 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
count, COL['dataType']).setCurrentText(r['dataType']) count, COL['dataType']).setCurrentText(r['dataType'])
self.soh_list_table_widget.item( self.soh_list_table_widget.item(
count, COL['preferredSOHs']).setText(r['preferredSOHs']) count, COL['preferredSOHs']).setText(r['preferredSOHs'])
if r['default'] == 1:
self.set_default_row(count)
if r['current'] == 1: if r['current'] == 1:
self.curr_sel_changed(count) self.curr_sel_changed(count)
self.soh_list_table_widget.selectRow(count) self.soh_list_table_widget.selectRow(count)
count += 1 count += 1
self.update() 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): def get_row(self, row_idx):
""" """
Get content of a self.soh_list_table_widget's row Get content of a self.soh_list_table_widget's row
...@@ -744,6 +783,6 @@ class ChannelPreferDialog(OneWindowAtATimeDialog): ...@@ -744,6 +783,6 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
:return id_rows: [dict,] - list of data for each row :return id_rows: [dict,] - list of data for each row
""" """
id_rows = execute_db_dict( id_rows = execute_db_dict(
"SELECT name, preferredSOHs, dataType, current FROM ChannelPrefer " "SELECT * FROM ChannelPrefer "
" ORDER BY name ASC") " ORDER BY name ASC")
return id_rows return id_rows
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 ...@@ -15,6 +15,7 @@ from sohstationviewer.model.data_loader import DataLoader
from sohstationviewer.model.general_data.general_data import \ from sohstationviewer.model.general_data.general_data import \
GeneralData GeneralData
from sohstationviewer.view.about_dialog import AboutDialog
from sohstationviewer.view.calendar.calendar_dialog import CalendarDialog from sohstationviewer.view.calendar.calendar_dialog import CalendarDialog
from sohstationviewer.view.db_config.channel_dialog import ChannelDialog from sohstationviewer.view.db_config.channel_dialog import ChannelDialog
from sohstationviewer.view.db_config.data_type_dialog import DataTypeDialog from sohstationviewer.view.db_config.data_type_dialog import DataTypeDialog
...@@ -22,7 +23,9 @@ from sohstationviewer.view.db_config.param_dialog import ParamDialog ...@@ -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.db_config.plot_type_dialog import PlotTypeDialog
from sohstationviewer.view.file_information.get_file_information import \ from sohstationviewer.view.file_information.get_file_information import \
extract_data_set_info 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 \ from sohstationviewer.view.plotting.gps_plot.extract_gps_data import \
extract_gps_data extract_gps_data
from sohstationviewer.view.plotting.gps_plot.gps_dialog import GPSDialog from sohstationviewer.view.plotting.gps_plot.gps_dialog import GPSDialog
...@@ -192,6 +195,10 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow): ...@@ -192,6 +195,10 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
""" """
self.help_browser: HelpBrowser = HelpBrowser() 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. search_message_dialog: Display log, soh message with searching feature.
""" """
self.search_message_dialog: SearchMessageDialog = SearchMessageDialog() self.search_message_dialog: SearchMessageDialog = SearchMessageDialog()
...@@ -214,6 +221,14 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow): ...@@ -214,6 +221,14 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
self.yyyy_mm_dd_action.trigger() 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() @QtCore.Slot()
def save_plot(self): def save_plot(self):
self.plotting_widget.save_plot('SOH-Plot') self.plotting_widget.save_plot('SOH-Plot')
...@@ -420,9 +435,8 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow): ...@@ -420,9 +435,8 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
the user can load data. The starting directory is taken from the user can load data. The starting directory is taken from
curr_dir_line_edit. curr_dir_line_edit.
""" """
fd = QtWidgets.QFileDialog(self) fd = FileDialogForLinuxExternalDrive(
fd.setFileMode(QtWidgets.QFileDialog.FileMode.Directory) self, self.curr_dir_line_edit.text())
fd.setDirectory(self.curr_dir_line_edit.text())
fd.exec() fd.exec()
if fd.result() == QtWidgets.QDialog.DialogCode.Accepted: if fd.result() == QtWidgets.QDialog.DialogCode.Accepted:
new_path = fd.selectedFiles()[0] new_path = fd.selectedFiles()[0]
...@@ -623,6 +637,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow): ...@@ -623,6 +637,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
Read data from selected files/directories, process and plot channels Read data from selected files/directories, process and plot channels
read from those according to current options set on the GUI 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) self.plot_diff_data_set_id_button.setEnabled(False)
display_tracking_info(self.tracking_info_text_browser, display_tracking_info(self.tracking_info_text_browser,
"Loading started", "Loading started",
...@@ -891,6 +906,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow): ...@@ -891,6 +906,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
Plot using data from self.data_object with the current options set Plot using data from self.data_object with the current options set
from GUI from GUI
""" """
self.replot_button.setEnabled(False)
self.clear_plots() self.clear_plots()
if self.has_problem: if self.has_problem:
return return
......
...@@ -314,8 +314,8 @@ class MultiThreadedPlottingWidget(PlottingWidget): ...@@ -314,8 +314,8 @@ class MultiThreadedPlottingWidget(PlottingWidget):
finished_msg = f'{self.name} plot finished.' finished_msg = f'{self.name} plot finished.'
display_tracking_info(self.tracking_box, finished_msg, LogType.INFO) display_tracking_info(self.tracking_box, finished_msg, LogType.INFO)
self.is_working = False self.is_working = False
self.handle_replot_button()
def done(self): def done(self):
""" """
......
...@@ -477,6 +477,10 @@ class Plotting: ...@@ -477,6 +477,10 @@ class Plotting:
ax = self.plotting_axes.create_axes( ax = self.plotting_axes.create_axes(
self.parent.plotting_bot, plot_h) self.parent.plotting_bot, plot_h)
x_list, y_list = c_data['times'], c_data['data'] 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]) total_x = sum([len(x) for x in x_list])
self.plotting_axes.set_axes_info( self.plotting_axes.set_axes_info(
...@@ -500,8 +504,7 @@ class Plotting: ...@@ -500,8 +504,7 @@ class Plotting:
# ax.chan_plots. Also scatter is different with ax.plot and # ax.chan_plots. Also scatter is different with ax.plot and
# should be treated in a different way if want to replot. # should be treated in a different way if want to replot.
ax.x_center = x_list[0] ax.x_center = x_list[0]
ax.y_center = apply_convert_factor( ax.y_center = y_list[0]
y_list, chan_id, self.main_window.data_type)[0]
ax.chan_db_info = chan_db_info ax.chan_db_info = chan_db_info
return ax return ax
......
...@@ -192,7 +192,7 @@ def get_colors_sizes_for_abs_y_from_value_colors( ...@@ -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 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 Convert data according to convert_factor got from DB
......
...@@ -407,6 +407,10 @@ class PlottingWidget(QtWidgets.QScrollArea): ...@@ -407,6 +407,10 @@ class PlottingWidget(QtWidgets.QScrollArea):
+ If the chan_data has key 'logIdx', raise the Search Messages dialog, + If the chan_data has key 'logIdx', raise the Search Messages dialog,
focus SOH tab, roll to the corresponding line. 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 self.is_button_press_event_triggered_pick_event = True
artist = event.artist artist = event.artist
self.log_idxes = None self.log_idxes = None
...@@ -507,7 +511,11 @@ class PlottingWidget(QtWidgets.QScrollArea): ...@@ -507,7 +511,11 @@ class PlottingWidget(QtWidgets.QScrollArea):
self.ruler_text = None self.ruler_text = None
except AttributeError: except AttributeError:
pass 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: for w in self.peer_plotting_widgets:
if not w.has_data: if not w.has_data:
continue continue
...@@ -840,3 +848,16 @@ class PlottingWidget(QtWidgets.QScrollArea): ...@@ -840,3 +848,16 @@ class PlottingWidget(QtWidgets.QScrollArea):
self.draw() self.draw()
except AttributeError: except AttributeError:
pass 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)
# Display time-power-squared values for waveform data # Display time-power-squared values for waveform data
from math import sqrt
from typing import Union, Tuple, Dict, List from typing import Union, Tuple, Dict, List
from PySide6 import QtWidgets, QtCore from PySide6 import QtWidgets, QtCore
...@@ -6,17 +7,23 @@ from PySide6.QtCore import QEventLoop, Qt, QSize ...@@ -6,17 +7,23 @@ from PySide6.QtCore import QEventLoop, Qt, QSize
from PySide6.QtGui import QCursor from PySide6.QtGui import QCursor
from PySide6.QtWidgets import QApplication, QTabWidget 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 get_color_def, get_color_ranges
)
from sohstationviewer.model.general_data.general_data import GeneralData
from sohstationviewer.view.plotting.time_power_square.\ from sohstationviewer.view.plotting.time_power_square.\
time_power_squared_widget import TimePowerSquaredWidget 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.\ from sohstationviewer.view.plotting.time_power_square.\
time_power_squared_helper import get_start_5mins_of_diff_days time_power_squared_helper import \
from sohstationviewer.conf.constants import DAY_LIMIT_FOR_TPS_IN_ONE_TAB get_start_5mins_of_diff_days, find_tps_tm_idx
class TimePowerSquaredDialog(QtWidgets.QWidget): class TimePowerSquaredDialog(QtWidgets.QWidget):
...@@ -346,3 +353,30 @@ class TimePowerSquaredDialog(QtWidgets.QWidget): ...@@ -346,3 +353,30 @@ class TimePowerSquaredDialog(QtWidgets.QWidget):
tps_widget.plot_channels( tps_widget.plot_channels(
data_dict, data_set_id, self.start_5mins_of_diff_days, data_dict, data_set_id, self.start_5mins_of_diff_days,
self.min_x, self.max_x) 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)
from math import sqrt
from typing import List, Tuple, Union, Dict, Optional from typing import List, Tuple, Union, Dict, Optional
import numpy as np import numpy as np
from PySide6 import QtCore from PySide6 import QtCore
from matplotlib.axes import Axes from matplotlib.axes import Axes
from matplotlib.lines import Line2D from matplotlib.lines import Line2D
from matplotlib.backend_bases import PickEvent
from sohstationviewer.conf import constants as const from sohstationviewer.conf import constants as const
from sohstationviewer.controller.plotting_data import ( from sohstationviewer.controller.plotting_data import (
get_title, get_day_ticks, format_time, get_title, get_day_ticks
) )
from sohstationviewer.controller.util import ( from sohstationviewer.controller.util import (
display_tracking_info, add_thousand_separator, display_tracking_info
) )
from sohstationviewer.database.extract_data import ( from sohstationviewer.database.extract_data import (
get_seismic_chan_label, get_seismic_chan_label,
...@@ -266,6 +266,7 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget): ...@@ -266,6 +266,7 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
self.set_lim_markers() self.set_lim_markers()
self.draw() self.draw()
self.is_working = False self.is_working = False
self.handle_replot_button()
def plot_channel(self, c_data: str, chan_id: str) -> Axes: def plot_channel(self, c_data: str, chan_id: str) -> Axes:
""" """
...@@ -410,65 +411,85 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget): ...@@ -410,65 +411,85 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
ax.patch.set_alpha(0) ax.patch.set_alpha(0)
return ax return ax
def on_pick_event(self, event): def on_pick_event(self, event: PickEvent):
""" """
When a plot is select, the corresponding point on each plot will This function is called when a point is selected.
be highlighted and their time and counts will be displayed.
:param event: pick event - event when object of canvas is selected. To avoid redundant process, return for scroll event, or any modifiers
other than ctrl/cmd/no modifier.
https://matplotlib.org/stable/users/explain/figure/event_handling.html#event-attributes # noqa: E501
This function help set indexes in tps_dialog so all other tabs will use
the same indexes.
:param event: event when object of canvas is selected.
The event happens before button_press_event. The event happens before button_press_event.
""" """
if event.mouseevent.name == 'scroll_event': if event.mouseevent.name == 'scroll_event':
return return
if event.mouseevent.button in ('up', 'down'): if event.mouseevent.button in ('up', 'down'):
return return
info_str = "" modifiers = event.guiEvent.modifiers()
if modifiers not in [QtCore.Qt.KeyboardModifier.ControlModifier,
QtCore.Qt.KeyboardModifier.MetaModifier,
QtCore.Qt.KeyboardModifier.NoModifier]:
# skip modifiers other than Ctrl or command
return
if event.artist in self.axes: if event.artist in self.axes:
self.parent.vertical_scroll_pos = self.verticalScrollBar().value() self.parent.vertical_scroll_pos = self.verticalScrollBar().value()
xdata = event.mouseevent.xdata xdata = event.mouseevent.xdata
if xdata is None: if xdata is None:
return return
# clicked point's x value is the 5m index in a day xdata = round(xdata)
self.parent.five_minute_idx = xdata = round(xdata)
# when click on outside xrange that close to edge, adjust to edge # when click on outside xrange that close to edge, adjust to edge
if xdata in [-2, -1]: if xdata in [-2, -1]:
xdata = 0 xdata = 0
if xdata in [288, 289]: if xdata in [288, 289]:
xdata = 287 xdata = 287
# clicked point's y value which is the day index
self.parent.day_idx = ydata = round(event.mouseevent.ydata) # clicked point's x value is the 5m index in a day
# refer to description in plot_channel to understand x,y vs five_minute_index = xdata
# day_index, five_min_index
day_index = - ydata ydata = round(event.mouseevent.ydata)
five_min_index = xdata # Clicked point's y value is corresponding to the day index but
# negative because days are plotted from top to bottom.
# If click in the area above first day, set day_index to 0 or
# it will highlight day 1 which isn't close to the clicked point.
day_index = abs(ydata) if ydata <= 0 else 0
try: try:
# identify time for rulers on other plotting widget # Assign tps_t to be use as xdata or real timestamp
# from plotting_widget.button_press_event() (super class)
self.tps_t = self.start_5mins_of_diff_days[ self.tps_t = self.start_5mins_of_diff_days[
day_index, five_min_index] day_index, five_minute_index]
format_t = format_time(self.tps_t, self.date_mode, 'HH:MM:SS')
info_str += f"<pre>{format_t}:"
for tps_widget in self.parent.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'][day_index, five_min_index]
info_str += (f" {chan_id}:"
f"{add_thousand_separator(sqrt(data))}")
info_str += " (counts)</pre>"
display_tracking_info(self.tracking_box, info_str)
self.draw()
except IndexError: except IndexError:
# exclude the extra points added to the 2 sides of x axis to # exclude the extra points added to the 2 sides of x axis to
# show the entire highlight box # show the entire highlight box
pass pass
def on_ctrl_cmd_click(self, xdata): def on_ctrl_cmd_click(self, timestamp):
""" """
Ctrl + cmd: using parent's five_minute_idx and day_idx to position the If Ctrl/Cmd + click on this widget, on_pick_event() will be called
tps widget's ruler. first and set self.tps_data.
(equal to find_tps_tm_idx(xdata, self.start_5mins_of_diff_days)
:param xdata: float - time value in other plot on_button_press_event() can use xdata, if called from waveform or soh
widget, or tps_data, if called from tps widget, as real timestamp to
pass to tps_dlg.set_indexes_and_display_info() for computing
five_minute_idx/day_idx and displaying info of all tps channels' points
The above tasks are placed in tps_dlg so that it won't be processed
repeatedly.
on_button_press_event() also loops through the tps_widgets to call this
function to place the highlighting box (ruler) for each of them
according to the indexes calculated in the tps_dlg.
:param timestamp: real timestamp value to be consistent with
on_ctrl_cmd_click in other plotting_widgets.
""" """
self.zoom_marker1_shown = False self.zoom_marker1_shown = False
# locate the ruler on each channel in the tab to highlight the point
for rl in self.rulers: for rl in self.rulers:
rl.set_data(self.parent.five_minute_idx, self.parent.day_idx) rl.set_data(self.parent.five_minute_idx, self.parent.day_idx)
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>About</class>
<widget class="QDialog" name="About">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>159</height>
</rect>
</property>
<property name="windowTitle">
<string>About</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QTextEdit" name="aboutTextEdit">
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(239, 240, 241);</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="html">
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:12pt; font-weight:600;&quot;&gt;SOH StationViewer&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:12pt; font-weight:600;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;This is software is distributed free of charge.&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;This text is a placeholder. Please change me.&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Copyright © IRIS PASSCAL 2021&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>About</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>About</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'about.ui',
# licensing of 'about.ui' applies.
#
# Created: Wed Jun 2 17:01:59 2021
# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_About(object):
def setupUi(self, About):
About.setObjectName("About")
About.resize(400, 159)
self.gridLayout = QtWidgets.QGridLayout(About)
self.gridLayout.setObjectName("gridLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.buttonBox = QtWidgets.QDialogButtonBox(About)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.horizontalLayout.addWidget(self.buttonBox)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
self.gridLayout.addLayout(self.horizontalLayout, 2, 1, 1, 1)
self.aboutTextEdit = QtWidgets.QTextEdit(About)
self.aboutTextEdit.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
self.aboutTextEdit.setAcceptDrops(False)
self.aboutTextEdit.setAutoFillBackground(False)
self.aboutTextEdit.setStyleSheet("background-color: rgb(239, 240, 241);")
self.aboutTextEdit.setFrameShape(QtWidgets.QFrame.NoFrame)
self.aboutTextEdit.setFrameShadow(QtWidgets.QFrame.Plain)
self.aboutTextEdit.setUndoRedoEnabled(False)
self.aboutTextEdit.setReadOnly(True)
self.aboutTextEdit.setHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Sans Serif\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:12pt; font-weight:600;\">SOH StationViewer</span></p>\n"
"<p align=\"center\" style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:12pt; font-weight:600;\"><br /></p>\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">This is software is distributed free of charge.</p>\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">This text is a placeholder. Please change me.</p>\n"
"<p align=\"center\" style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Copyright © IRIS PASSCAL 2021</p>\n"
"<p align=\"center\" style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p align=\"center\" style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p></body></html>")
self.aboutTextEdit.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
self.aboutTextEdit.setObjectName("aboutTextEdit")
self.gridLayout.addWidget(self.aboutTextEdit, 1, 1, 1, 1)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem2, 0, 1, 1, 1)
self.retranslateUi(About)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), About.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), About.reject)
QtCore.QMetaObject.connectSlotsByName(About)
def retranslateUi(self, About):
About.setWindowTitle(QtWidgets.QApplication.translate("About", "About", None, -1))