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 (30)
Showing
with 236 additions and 153 deletions
......@@ -57,3 +57,27 @@ database editor
--------
* Fix a bug that happens when building on some Linux machines
* Bux fixes
2024.3.1.0
--------
* Improve performance for RT130 data sets when only a subset of all data
streams is read.
* Add the ability to zoom out in the plot.
* Add unused SOH channels (VGT, VPH, VPO, VPP, VPV) for Pegasus datalogger to
the database. These channels are used for an unsupported model of the Pegasus
datalogger.
* Plot points with value 255 in the VAN channel for Pegasus datalogger in gray
instead of red. This distinguish them from points with value 2.
* Limit the zoom range. This fixes a bug that happens when the zoom range is
too small.
* Remove support for matplotlib versions below 3.6.2.
* Pause support for numpy versions 2.0 and above. Support will resume when all
dependencies have been updated to support numpy 2.0 and above.
* Fix the button to plot a different station in the same data set not being
enabled when needed.
* Fix default rows in the channel preference dialog being converted to
non-default rows on save.
* Fix artifacts showing up in the plot with some field laptops.
* Fix problems reading RT130 data sets caused by choosing specific data streams
to read.
* Fix TPS plot being broken for data sets with high-amplitude waveforms.
package:
name: sohviewer
version: 2024.3.0.1
version: 2024.3.1.0
source:
path: ../
......@@ -15,10 +15,10 @@ requirements:
- pip
run:
- python >=3.9
- numpy>=1.23.0
- numpy >=1.23.0,<2.0
- obspy >=1.3.0
- PySide6>=6.5.2
- matplotlib>=3.5.0
- matplotlib>=3.6.2
test:
source_files:
......
......@@ -32,10 +32,10 @@ setup(
],
},
install_requires=[
'numpy>=1.23.0',
'numpy >=1.23.0,<2.0',
'obspy>=1.3.0',
'PySide6>=6.5.2',
'matplotlib>=3.5.0',
'matplotlib>=3.6.2',
],
setup_requires=[],
extras_require={
......@@ -51,6 +51,6 @@ setup(
name='sohviewer',
packages=find_packages(include=['sohstationviewer*']),
url='https://git.passcal.nmt.edu/software_public/passoft/sohstationviewer',
version='2024.3.0.1',
version='2024.3.1.0',
zip_safe=False,
)
......@@ -5,8 +5,8 @@ from typing import Literal
ROOT_PATH = Path(__file__).resolve().parent.parent
# The current version of SOHStationViewer
SOFTWARE_VERSION = '2024.3.0.1'
BUILD_TIME = "March 13, 2024"
SOFTWARE_VERSION = '2024.3.1.0'
BUILD_TIME = "August 15, 2024"
# waveform pattern
WF_1ST = 'A-HLM-V'
......
No preview for this file type
import re
from typing import Dict, List
from sohstationviewer.conf.constants import ColorMode
......@@ -26,7 +27,15 @@ def get_chan_plot_info(org_chan_id: str, data_type: str,
chan = convert_actual_channel_to_db_channel_w_question_mark(chan,
data_type)
if len(org_chan_id) == 3 and org_chan_id.startswith('DS'):
# RT130 waveform channels is formed by the string DS followed by the data
# stream number, a dash character, and the channel number.
# Doing incomplete checks for RT130 waveform channels have caused a bunch
# of problems before (see https://git.passcal.nmt.edu/software_public/passoft/sohstationviewer/-/merge_requests/240?diff_id=5092&start_sha=55ac101ffac8860ba66ac5e7b694e70ee397919c # noqa
# and https://git.passcal.nmt.edu/software_public/passoft/sohstationviewer/-/issues/287). # noqa
# So, we match RT130 waveform channels exactly with a regex to avoid these
# problems.
rt130_waveform_regex = re.compile(r'DS\d-\d')
if rt130_waveform_regex.match(org_chan_id):
chan = 'SEISMIC'
if dbConf['seisRE'].match(chan):
chan = 'SEISMIC'
......
No preview for this file type
......@@ -16,7 +16,9 @@ all the TPS channels. Ctr/cmd + Click on TPS plot will work the same.
---------------------------
# Zooming
Shift + Click to mark the first zooming point, then Shift + click again to mark
## Zoom in
Shift + Click on a plot to mark the first zooming point, then Shift + click again to mark
the second zooming point to zoom the area between two zooming points.
The zooming task will be performed in the area corresponding to zooming triggering
......@@ -31,3 +33,7 @@ be triggered from TPS dialog.
<br />
<br />
<br />
## Zoom out
Shift + Click on the left side of a plot to zoom out one level, which means
undoing one zoom in.
\ No newline at end of file
......@@ -269,7 +269,11 @@ class RT130(GeneralData):
def read_file(self, path2file: Path, file_name: str,
count: int, total: int) -> None:
"""
Read data or text from file
Read data or text from file.
Skip reading data if data belong to data stream that isn't requested.
Data stream can be detected based on folder structure:
[YYYYDOY]/[das_serial_number]/[data_stream]/[filename]
:param path2file: absolute path to file
:param file_name: name of file
:param count: total number of file read
......@@ -282,6 +286,14 @@ class RT130(GeneralData):
if log_text is not None:
self.log_texts['TEXT'].append(log_text)
return
if self.req_data_streams != ['*']:
# filter non selected data stream
ds = path2file.parts[-2]
if ds in ['1', '2', '3', '4', '5', '6', '7', '8']:
# Check if folder name match format of data stream's folder
if int(ds) not in self.req_data_streams:
# Check if data stream is required
return
self.read_reftek_130(path2file)
def select_data_set_id(self) -> Tuple[str, str]:
......
......@@ -335,7 +335,7 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
self.soh_list_table_widget.item(
count, COL['preferredSOHs']).setText(r['preferredSOHs'])
if r['default'] == 1:
if r['isDefault'] == 1:
self.set_default_row(count)
if r['current'] == 1:
......@@ -508,9 +508,13 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
"""
selected_row_idx = self.soh_list_table_widget.indexFromItem(
self.soh_list_item
).row()
row_edit_button = self.soh_list_table_widget.cellWidget(
selected_row_idx, COL['edit']
)
# Hardcoding the check for default row because there is no time.
if selected_row_idx.row() < 4:
is_default_row = not row_edit_button.isEnabled()
if is_default_row:
err_msg = ('The selected row is a default row and cannot be '
'overwritten. Please select a non-default row and try '
'again.')
......@@ -732,6 +736,12 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
row_idx, COL['dataType']).currentText()
preferred_sohs = self.soh_list_table_widget.item(
row_idx, COL['preferredSOHs']).text()
row_edit_button = self.soh_list_table_widget.cellWidget(
row_idx, COL['edit']
)
# The edit button is only disabled for default rows, so we can use its
# state to determine if a row is a default row.
is_default_row = not row_edit_button.isEnabled()
if preferred_sohs.strip() == '' and name.strip() == '':
return '', '', ''
display_id = row_idx + 1
......@@ -749,8 +759,10 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
QtWidgets.QMessageBox.information(self, "Missing info", msg)
return
sql = (f"INSERT INTO ChannelPrefer (name, preferredSOHs, dataType, "
f"current) VALUES "
f"('{name}', '{preferred_sohs}', '{data_type}', {current})")
f"current, isDefault) VALUES "
f"('{name}', '{preferred_sohs}', '{data_type}', {current}, "
f"{is_default_row})")
print(sql)
primary_key = name
return primary_key, str(display_id), sql
......
......@@ -67,16 +67,6 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
super().__init__(parent)
self.setup_ui(self)
"""
SCREEN INFO
actual_dpi: actual dpi of the screen where main_window is located.
actual_dpi = physical_dpi * device_pixel_ratio
dpi_y: vertical dpi of the screen
dpi_x: horizontal dpi of the screen
"""
self.actual_dpi: float = 100.
self.dpi_x: float = 25.
self.dpi_y: float = 30.
"""
dir_names: list of absolute paths of data sets
"""
self.list_of_dir: List[Path] = []
......@@ -617,19 +607,6 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
msg = "No directories have been selected."
raise Exception(msg)
try:
self.req_wf_chans = self.get_requested_wf_chans()
except Exception as e:
QMessageBox.information(self, "Waveform Selection", str(e))
self.cancel_loading()
return
if self.warn_big_file_sizes.isChecked():
# call check_folder_size() here b/c it requires list_of_dir and it
# is before the called for detect_data_type() which sometimes take
# quite a long time.
if not check_folders_size(self.list_of_dir, self.req_wf_chans):
raise Exception("Big size")
# Log files don't have a data type that can be detected, so we don't
# detect the data type if we are reading them.
if self.rt130_das_dict == {} and not self.log_checkbox.isChecked():
......@@ -643,6 +620,21 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
"Do you want to cancel to select different folder(s)\n"
"Or continue to read any available mseed file?")
raise Exception(msg)
try:
# get_requested_wf_chans have to be called after data_type is
# detected
self.req_wf_chans = self.get_requested_wf_chans()
except Exception as e:
QMessageBox.information(self, "Waveform Selection", str(e))
self.cancel_loading()
return
if self.warn_big_file_sizes.isChecked():
# call check_folder_size() here b/c it requires list_of_dir and it
# is before the called for detect_data_type() which sometimes take
# quite a long time.
if not check_folders_size(self.list_of_dir, self.req_wf_chans):
raise Exception("Big size")
def clear_plots(self):
self.plotting_widget.clear()
......@@ -1057,7 +1049,7 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
self.is_stopping = False
try:
if len(self.data_object.data_set_ids) > 1:
self.plot_diff_data_set_id.setEnabled(True)
self.plot_diff_data_set_id_button.setEnabled(True)
except AttributeError:
pass
......@@ -1409,19 +1401,6 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
self.open_files_list.insertItem(0, found_files_list_item)
# ======================== EVENTS ==========================
def moveEvent(self, event):
"""
Get dpi, dpi_x, dpi_y whenever main window is moved
"""
screen = QtWidgets.QApplication.screenAt(event.pos())
if screen is not None:
curr_actual_dpi = (
screen.physicalDotsPerInch() * screen.devicePixelRatio())
self.dpi_x = screen.physicalDotsPerInchX()
self.dpi_y = screen.physicalDotsPerInchY()
self.actual_dpi = curr_actual_dpi
return super().moveEvent(event)
def resizeEvent(self, event):
"""
......
......@@ -234,8 +234,8 @@ class MultiThreadedPlottingWidget(PlottingWidget):
channel_processor.stopped.connect(self.has_stopped)
def plot_channels(
self, d_obj, data_set_id, start_tm, end_tm,
time_ticks_total, pref_order=[]):
self, d_obj, data_set_id, start_tm=None, end_tm=None,
time_ticks_total=0, pref_order=[], keep_zoom=False):
"""
Prepare to plot waveform/SOH/mass-position data:
+ get_plotting_info: get sizing info
......@@ -256,6 +256,8 @@ class MultiThreadedPlottingWidget(PlottingWidget):
self.start_tm = start_tm
self.end_tm = end_tm
self.time_ticks_total = time_ticks_total
if not keep_zoom:
self.zoom_minmax_list = []
if 'VST' in pref_order:
# pref_order use original name VST to read from file.
......
"""
Class of which object is used to plot data
"""
from typing import List, Optional, Union
from typing import List, Optional, Union, Tuple
import math
import numpy as np
import matplotlib.text
......@@ -52,6 +52,16 @@ class PlottingWidget(QtWidgets.QScrollArea):
self.name = name
self.tracking_box = tracking_box
# =============== declare attributes =======================
"""
DPI INFO
actual_dpi: actual dpi of the screen where main_window is located.
actual_dpi = physical_dpi * device_pixel_ratio
dpi_y: vertical dpi of the screen
dpi_x: horizontal dpi of the screen
"""
self.actual_dpi: float = 100.
self.dpi_x: float = 25.
self.dpi_y: float = 30.
"""
has_data: indicate if there're any data to be plotted
"""
......@@ -71,6 +81,12 @@ class PlottingWidget(QtWidgets.QScrollArea):
self.min_x = 0
self.max_x = 0
"""
zoom_minmax_list: list of x ranges of zooming. The first range is the
original of x ranges. If there is more than one ranges the plotting has
been zoomed in.
"""
self.zoom_minmax_list: List[Tuple[float, float]] = []
"""
plotting_bot: float - bottom of a current plot, decrease by plot_h
return from self.get_height() whenever a new plot is added
"""
......@@ -241,6 +257,18 @@ class PlottingWidget(QtWidgets.QScrollArea):
top_space_in + bot_space_in +
2 * time_bar_height_in)
def set_dpi(self):
"""
Calculate dpi to correct sizing calculation
"""
screen = self.screen()
if screen is not None:
self.dpi_x = screen.physicalDotsPerInchX()
self.dpi_y = screen.physicalDotsPerInchY()
# Using dpi_x as standard
self.actual_dpi = (screen.physicalDotsPerInchX() *
self.devicePixelRatio())
def set_size(self, view_port_size: Optional[float] = None) -> None:
"""
Set figure's width and main widget's width to fit the width of the
......@@ -251,13 +279,14 @@ class PlottingWidget(QtWidgets.QScrollArea):
:param view_port_size: size of viewport
"""
self.set_dpi()
if view_port_size is None:
view_port_size = self.maximumViewportSize()
view_port_width = view_port_size.width()
self.plotting_axes.canvas.setFixedWidth(view_port_width)
self.fig_width_in = view_port_width/self.parent.dpi_x
self.fig_width_in = view_port_width/self.dpi_x
self.plotting_axes.fig.set_dpi(self.parent.actual_dpi)
self.plotting_axes.fig.set_dpi(self.actual_dpi)
# set view size fit with the scroll's viewport size
self.main_widget.setFixedWidth(view_port_size.width())
......@@ -265,7 +294,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
view_port_height = view_port_size.height()
self.main_widget.setFixedHeight(view_port_height)
self.plotting_axes.canvas.setFixedHeight(view_port_height)
fig_height_in = view_port_height/self.parent.dpi_y
fig_height_in = view_port_height/self.dpi_y
self.plotting_axes.fig.set_figheight(fig_height_in)
def plotting_preset(self):
......@@ -279,7 +308,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
Calculate height_normalizing_factor.
"""
self.set_size()
fig_height = math.ceil(self.fig_height_in * self.parent.dpi_y)
fig_height = math.ceil(self.fig_height_in * self.dpi_y)
# adjusting main_widget to fit view port
max_height = max(self.maximumViewportSize().height(), fig_height)
......@@ -340,11 +369,29 @@ class PlottingWidget(QtWidgets.QScrollArea):
:param xdata: float - time value in plot
"""
if self.new_min_x == xdata:
self.zoom_marker1.set_visible(False)
self.zoom_marker2.set_visible(False)
if abs(self.new_min_x - xdata) < 0.001:
# to prevent weird zoom in
display_tracking_info(
self.tracking_box, "Selected range is too small to zoom in.")
self.plotting_axes.canvas.draw()
return
self.zoom_marker1_shown = False
[self.min_x, self.max_x] = sorted([self.new_min_x, xdata])
self.set_lim()
self.plotting_axes.canvas.draw()
def zoom_out(self):
"""
Zoom out by setting limit to the previous range when there's at least
one zoom-in.
"""
if len(self.zoom_minmax_list) > 1:
self.min_x, self.max_x = self.zoom_minmax_list[-2]
self.zoom_minmax_list.pop()
self.set_lim(is_zoom_in=False)
self.zoom_marker1_shown = False
self.zoom_marker1.set_visible(False)
self.zoom_marker2.set_visible(False)
self.plotting_axes.canvas.draw()
......@@ -477,9 +524,9 @@ class PlottingWidget(QtWidgets.QScrollArea):
When click mouse on the current plottingWidget, SOHView will loop
through different plottingWidgets to do the same task for
interaction:
+ shift+click: call on_shift_click() to do zooming. This is
disregarded in TimePowerSquareWidget because it isn't subjected
to be zoomed in.
+ shift+click: is disregarded if start in TimePowerSquareWidget
* If click on left side of the plot (xdata<xmin): call zoom out
* Otherwise: call on_shift_click to do tasks of zoom in
+ ctrl+click or cmd+click in mac: call on_ctrl_cmd_click() to show
ruler
......@@ -510,16 +557,6 @@ class PlottingWidget(QtWidgets.QScrollArea):
else:
xdata = self.get_timestamp(event)
# We only want to remove the text on the ruler when we start zooming in
# or move the ruler to another location.
if modifiers in [QtCore.Qt.KeyboardModifier.ControlModifier,
QtCore.Qt.KeyboardModifier.MetaModifier,
QtCore.Qt.KeyboardModifier.ShiftModifier]:
try:
self.ruler_text.remove()
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,
......@@ -528,16 +565,14 @@ class PlottingWidget(QtWidgets.QScrollArea):
for w in self.peer_plotting_widgets:
if not w.has_data:
continue
if modifiers in [QtCore.Qt.KeyboardModifier.ControlModifier,
QtCore.Qt.KeyboardModifier.MetaModifier,
QtCore.Qt.KeyboardModifier.ShiftModifier]:
try:
w.ruler_text.remove()
w.ruler_text = None
except AttributeError:
pass
if modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier:
w.on_shift_click(xdata)
if xdata < w.min_x:
# click on left of plot
w.zoom_marker1_shown = False # reset zoom in
w.zoom_out()
else:
w.on_shift_click(xdata)
elif modifiers in [QtCore.Qt.KeyboardModifier.ControlModifier,
QtCore.Qt.KeyboardModifier.MetaModifier]:
w.on_ctrl_cmd_click(xdata)
......@@ -565,6 +600,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
:param xdata: float - time value of a channel plot
"""
self.zoom_marker1.set_visible(False)
self.zoom_marker1_shown = False
try:
......@@ -575,6 +611,11 @@ class PlottingWidget(QtWidgets.QScrollArea):
if xdata >= self.min_x:
ruler_text_content = format_time(xdata, self.parent.date_format,
'HH:MM:SS')
try:
# remove ruler_text before creating the new one
self.ruler_text.remove()
except AttributeError:
pass
self.ruler_text = self.plotting_axes.fig.text(
xdata, 5000, ruler_text_content,
verticalalignment='top',
......@@ -587,18 +628,20 @@ class PlottingWidget(QtWidgets.QScrollArea):
def on_shift_click(self, xdata):
"""
On shift + left click:
if click on the left of a plot, do zoom out to the previous range
if zoom_marker1 not shown yet:
+ hide ruler
+ connect zoom_marker2 to follow mouse
+ show zoom_marker1
else:
+ show zoom_marker2
+ zoom data in between 2 zoomMarkers
Notice that ruler will stay at the same xdata either with zoom in
or out.
:param xdata: float - time value of a channel plot
"""
if not self.zoom_marker1_shown:
self.ruler.set_visible(False)
self.set_ruler_visibled(self.zoom_marker1, xdata)
self.new_min_x = xdata
self.zoom_marker1_shown = True
......@@ -629,6 +672,8 @@ class PlottingWidget(QtWidgets.QScrollArea):
When press on Escape key, hide all rulers and set False for
zoom_marker1_shown on all plotting widgets
Notice: press Escape is the only way to hide ruler and its text.
:param event: QKeyEvent - event to know what key is pressed
"""
if event.key() == QtCore.Qt.Key.Key_Escape:
......@@ -673,17 +718,26 @@ class PlottingWidget(QtWidgets.QScrollArea):
or min(gap) <= self.min_x <= max(gap)
or min(gap) <= self.max_x <= max(gap))
def set_lim(self, first_time=False):
def set_lim(self, first_time=False, is_zoom_in=True):
"""
+ Re-decimate channel data to get more detail when zoom in
(get_zoom_data)
+ Append to zoom_minmax_list if called from a zoom_in. First time
plotting is considered a zoom_in to create first x range in the list.
+ Update timestamp bar with new ticks info
+ Update gap_bar by setting xlim and re-calculate gap total.
+ for each axes, set new x_lim, y_lim, label for totals of data points
:param first_time: bool - flag shows that this set_lim is called the
fist time for this data set or not.
:param is_zoom_in: if set_lim comes from zoom_in task
"""
from_add_remove_channels = False
if is_zoom_in:
if first_time and self.zoom_minmax_list:
self.min_x, self.max_x = self.zoom_minmax_list[-1]
from_add_remove_channels = True
else:
self.zoom_minmax_list.append((self.min_x, self.max_x))
self.plotting_axes.update_timestamp_bar(self.timestamp_bar_top)
self.plotting_axes.update_timestamp_bar(self.timestamp_bar_bottom)
if self.gap_bar is not None:
......@@ -703,7 +757,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
break
ax.set_xlim(self.min_x, self.max_x)
if not first_time:
if not first_time or from_add_remove_channels:
new_min_y = None
new_max_y = None
if hasattr(ax, 'x_top'):
......
......@@ -4,7 +4,7 @@ from math import sqrt
from typing import Union, Tuple, Dict
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import QEventLoop, Qt, QSize
from PySide6.QtCore import QEventLoop, Qt
from PySide6.QtGui import QCursor
from PySide6.QtWidgets import QApplication, QTabWidget
......@@ -38,18 +38,6 @@ class TimePowerSquaredDialog(QtWidgets.QWidget):
super().__init__()
self.main_window = parent
"""
SCREEN INFO
screen_size: size of screen where tps dialog is located
actual_dpi: actual dpi of the screen where tps dialog is located.
actual_dpi = physical_dpi * device_pixel_ratio
dpi_y: vertical dpi of the screen
dpi_x: horizontal dpi of the screen
"""
self.screen_size: QSize = None
self.actual_dpi: float = 100.
self.dpi_x: float = 25.
self.dpi_y: float = 30.
"""
data_type: str - type of data being plotted
"""
self.data_type = None
......@@ -203,20 +191,6 @@ class TimePowerSquaredDialog(QtWidgets.QWidget):
pass
return super(TimePowerSquaredDialog, self).resizeEvent(event)
def moveEvent(self, event):
"""
Get actual dpi, dpi_x, dpi_y whenever tps dialog is moved
"""
screen = QtWidgets.QApplication.screenAt(event.pos())
if screen is not None:
curr_actual_dpi = (
screen.physicalDotsPerInch() * screen.devicePixelRatio())
self.dpi_x = screen.physicalDotsPerInchX()
self.dpi_y = screen.physicalDotsPerInchY()
self.actual_dpi = curr_actual_dpi
return super().moveEvent(event)
def connect_signals(self):
"""
Connect functions to widgets
......
......@@ -109,9 +109,10 @@ def get_tps_for_discontinuous_data(
mean_squares = np.zeros_like(start_5min_blocks, dtype=float)
# Calculate mean_square for each 5m block
# adding dtype=float64 to prevent integer overflow
for i, d in enumerate(split_data):
if len(d) != 0:
mean_squares[i] = np.mean(np.square(d))
mean_squares[i] = np.mean(np.square(d, dtype='float64'))
elif ((0 < i < len(split_data) - 1) and
len(split_data[i - 1]) > 0 and len(split_data[i + 1]) > 0):
"""
......@@ -119,7 +120,8 @@ def get_tps_for_discontinuous_data(
data in the previous and next blocks if they both have data.
"""
mean_squares[i] = np.mean(np.square(
np.hstack((split_data[i - 1], split_data[i + 1]))
np.hstack((split_data[i - 1], split_data[i + 1])),
dtype='float64'
))
# reshape 1D mean_quares into 2D array in which each row contains 288 of
# 5m blocks' mean_squares of a day
......
......@@ -556,7 +556,7 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
self.zoom_marker1_shown = False
# locate the ruler on each channel in the tab to highlight the point
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])
def on_shift_click(self, xdata):
"""
......@@ -579,6 +579,16 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
self.set_lim_markers()
self.zoom_marker1_shown = False
def zoom_out(self):
"""
Zoom out by setting limit to the previous range when there's at least
one zoom-in.
"""
if len(self.zoom_minmax_list) > 1:
self.min_x, self.max_x = self.zoom_minmax_list[-2]
self.zoom_minmax_list.pop()
self.set_lim_markers(is_zoom_in=False)
def set_rulers_invisible(self):
"""
Clear data for self.rulers to make them disappeared.
......@@ -586,22 +596,30 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
for rl in self.rulers:
rl.set_data([], [])
def set_lim_markers(self):
def set_lim_markers(self, is_zoom_in=True):
"""
Find x index (which index in five minutes of a day) and
y index (which day) of self.min_x and self.min_y, and set data for
all markers in self.zoom_marker1s and self.zoom_marker2s.
+ Append to zoom_minmax_list if called from a zoom_in. First time
plotting is considered a zoom_in to create first x range in the list.
+ Find x index (which index in five minutes of a day) and
y index (which day) of self.min_x and self.min_y, and set data for
all markers in self.zoom_marker1s and self.zoom_marker2s.
:param is_zoom_in: if set_lim comes from zoom_in task
"""
if is_zoom_in:
self.zoom_minmax_list.append((self.min_x, self.max_x))
five_minute_idx, day_idx = find_tps_tm_idx(self.min_x,
self.start_5min_blocks,
self.start_first_day)
for zm1 in self.zoom_marker1s:
zm1.set_data(five_minute_idx, - day_idx)
zm1.set_data([five_minute_idx], [-day_idx])
five_minute_idx, day_idx = find_tps_tm_idx(self.max_x,
self.start_5min_blocks,
self.start_first_day)
for zm2 in self.zoom_marker2s:
zm2.set_data(five_minute_idx, - day_idx)
zm2.set_data([five_minute_idx], [-day_idx])
def request_stop(self):
"""Request all running channel processors to stop."""
......
......@@ -68,16 +68,6 @@ class WaveformDialog(QtWidgets.QWidget):
"""
self.parent = parent
"""
SCREEN INFO
actual_dpi: actual dpi of the screen where waveform dialog is located.
actual_dpi = physical_dpi * device_pixel_ratio
dpi_y: vertical dpi of the screen
dpi_x: horizontal dpi of the screen
"""
self.actual_dpi: float = 100.
self.dpi_x: float = 25.
self.dpi_y: float = 30.
"""
data_type: str - type of data being plotted
"""
self.data_type = None
......@@ -139,20 +129,6 @@ class WaveformDialog(QtWidgets.QWidget):
self.plotting_widget.init_size()
return super(WaveformDialog, self).resizeEvent(event)
def moveEvent(self, event):
"""
Get dpi, dpi_x, dpi_y whenever main window is moved
"""
screen = QtWidgets.QApplication.screenAt(event.pos())
if screen is not None:
curr_actual_dpi = (
screen.physicalDotsPerInch() * screen.devicePixelRatio())
self.dpi_x = screen.physicalDotsPerInchX()
self.dpi_y = screen.physicalDotsPerInchY()
self.actual_dpi = curr_actual_dpi
return super().moveEvent(event)
@QtCore.Slot()
def save_plot(self):
"""
......
......@@ -153,7 +153,9 @@ class SelectChanelsToShowDialog(QDialog):
self.parent.start_tm,
self.parent.end_tm,
self.parent.time_ticks_total,
new_pref_order)
new_pref_order,
keep_zoom=True
)
self.parent.draw()
self.close()
......@@ -65,7 +65,7 @@ class TestGetChanPlotInfo(BaseTestCase):
with self.subTest("RT130 Seismic"):
expected_result = {'param': 'Seismic data',
'dbChannel': 'SEISMIC',
'channel': 'DS2',
'channel': 'DS2-1',
'plotType': 'linesSRate',
'height': 8,
'unit': '',
......@@ -73,8 +73,8 @@ class TestGetChanPlotInfo(BaseTestCase):
'dbLabel': None,
'fixPoint': 0,
'valueColors': '',
'label': 'DS2'}
self.assertDictEqual(get_chan_plot_info('DS2', 'RT130'),
'label': 'DS2-1'}
self.assertDictEqual(get_chan_plot_info('DS2-1', 'RT130'),
expected_result)
with self.subTest("MSeed Seismic"):
......
......@@ -156,6 +156,19 @@ class TestGetTPSForDiscontinuousData(BaseTestCase):
# last block of day0 has value
self.assertIn(const.NUMBER_OF_5M_IN_DAY - 1, day0_indexes)
def test_overflow_data(self):
times = np.arange(self.start, self.end, 9*60) # 9m apart
# This data reproduce overflow data
data = np.random.randint(-10**6, 0, times.size, dtype='i4')
channel_data = {'tracesInfo': [{'times': times, 'data': data}]}
tps = get_tps_for_discontinuous_data(
channel_data, self.start_5mins_blocks)
self.assertEqual(len(tps), 2)
# Before adding dtype in np.square in get_tps_for_discontinuous_data,
# some tps data would be less than 0
lessthanzero_indexes = np.where(tps[0] < 0)[0]
self.assertEqual(lessthanzero_indexes.size, 0)
class TestGetTPSTimeByColorForADay(BaseTestCase):
def setUp(self):
......