Skip to content
Snippets Groups Projects

Multi-thread TPS plot

Merged Kien Le requested to merge featured-threading_TPS_plot into master
1 unresolved thread
3 files
+ 38
6
Compare changes
  • Side-by-side
  • Inline
Files
3
# Display time-power-squared values for waveform data
from math import sqrt
import numpy as np
from typing import List
import numpy as np
from PySide2 import QtWidgets, QtCore
from sohstationviewer.view.plotting.plotting_widget import plotting_widget
from sohstationviewer.view.util.color import clr
from sohstationviewer.conf import constants as const
from sohstationviewer.controller.plotting_data import (
get_title, get_day_ticks, format_time)
get_title, get_day_ticks, format_time,
)
from sohstationviewer.controller.util import (
display_tracking_info, add_thousand_separator
display_tracking_info, add_thousand_separator,
)
from sohstationviewer.model.handling_data import (
get_trim_tps_data, get_each_day_5_min_list, find_tps_tm)
from sohstationviewer.database.extract_data import (
get_color_def, get_color_ranges, get_chan_label)
from sohstationviewer.conf import constants as const
get_color_def, get_color_ranges, get_chan_label,
)
from sohstationviewer.model.handling_data import (
get_each_day_5_min_list, find_tps_tm,
)
from sohstationviewer.view.plotting.plotting_widget import plotting_widget
from sohstationviewer.view.plotting.time_power_squared_processor import (
TimePowerSquaredProcessor,
)
from sohstationviewer.view.util.color import clr
class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
stopped = QtCore.Signal()
"""
Widget to display time power square data for waveform channels
"""
@@ -56,6 +60,17 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
"""
self.tps_t = 0
self.tps_processors: List[TimePowerSquaredProcessor] = []
# The list of all channels that are processed.
self.channels = []
# The list of channels that have been processed.
self.processed_channels = []
# The post-processing step does not take too much time so there is no
# need to limit the number of threads that can run at once.
self.thread_pool = QtCore.QThreadPool()
self.finished_lock = QtCore.QMutex()
super().__init__(*args, **kwarg)
def plot_channels(self, start_tm, end_tm, key,
@@ -71,6 +86,12 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
:param waveform_data: dict - read waveform data of selected data set,
refer to DataTypeModel.__init__.waveform_data[key]['read_data']
"""
self.processed_channels = []
self.channels = []
self.tps_processors = []
start_msg = 'Plotting TPS data...'
display_tracking_info(self.tracking_box, start_msg)
self.processing_log = [] # [(message, type)]
self.gap_bar = None
self.min_x = max(data_time[0], start_tm)
@@ -78,6 +99,7 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
if self.axes:
self.plotting_axes.fig.clear()
self.draw()
self.date_mode = self.parent.date_format.upper()
if waveform_data == {}:
title = "NO WAVEFORM DATA TO DISPLAY."
@@ -104,8 +126,60 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
self.each_day5_min_list = get_each_day_5_min_list(self.min_x,
self.max_x)
for chan_id in self.plotting_data1:
ax = self.get_plot_data(self.plotting_data1[chan_id], chan_id)
c_data = self.plotting_data1[chan_id]
if 'tps_data' not in c_data:
self.channels.append(chan_id)
channel_processor = TimePowerSquaredProcessor(
chan_id, c_data, self.min_x, self.max_x,
self.each_day5_min_list
)
channel_processor.signals.finished.connect(self.channel_done)
channel_processor.signals.stopped.connect(self.channel_done)
self.tps_processors.append(channel_processor)
# Because the widget determine if processing is done by comparing the
# lists of scheduled and finished channels, if a channel runs fast
# enough that it finishes before any other channel can be scheduled,
# it will be the only channel executed. To prevent this, we tell the
# threadpool to only start running the processors once all channels
# have been scheduled.
for processor in self.tps_processors:
self.thread_pool.start(processor)
@QtCore.Slot()
def channel_done(self, chan_id: str):
"""
Slot called when a TPS processor is finished. Plot the TPS data of
channel chan_id if chan_id is not an empty string and add chan_id to
the list of processed of channels. If the list of processed channels
is the same as the list of all channels, notify the user that the
plotting is finished and add finishing touches to the plot.
If chan_id is the empty string, notify the user that the plotting has
been stopped.
:param chan_id: the name of the channel whose TPS data was processed.
If the TPS plot is stopped before it is finished, this will be the
empty string
"""
self.finished_lock.lock()
if chan_id != '':
ax = self.plot_channel(self.plotting_data1[chan_id], chan_id)
self.axes.append(ax)
self.processed_channels.append(chan_id)
if len(self.processed_channels) == len(self.channels):
if chan_id == '':
stopped_msg = 'TPS plot stopped.'
display_tracking_info(self.tracking_box, stopped_msg)
else:
finished_msg = 'TPS plot finished.'
display_tracking_info(self.tracking_box, finished_msg)
self.done()
self.stopped.emit()
self.finished_lock.unlock()
def done(self):
"""Add finishing touches to the plot and display it on the screen."""
self.set_legend()
# Set view size fit with the given data
if self.main_widget.geometry().height() < self.plotting_bot_pixel:
@@ -113,7 +187,7 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
self.set_lim_markers()
self.draw()
def get_plot_data(self, c_data, chan_id):
def plot_channel(self, c_data, chan_id):
"""
TPS is plotted in lines of small rectangular, so called bars.
Each line is a day so - y value is the order of days
@@ -125,10 +199,8 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
based on mapping between tps value of the five minutes against
the selected color range.
This function trim data to minx, max_x and calculate time-power-square
for each 5 minute into c_data['tps_data'] then draw each 5 minute
with the color corresponding to value.
Create ruler, zoom_marker1, zoom_marker2 for the channel.
This function draws each 5 minute with the color corresponding to
value and create ruler, zoom_marker1, and zoom_marker2 for the channel.
:param c_data: dict - data of the channel which includes down-sampled
data in keys 'times' and 'data'. Refer to
@@ -136,10 +208,7 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
:param chan_id: str - name of channel
:return ax: matplotlib.axes.Axes - axes of the channel
"""
if 'tps_data' not in c_data:
# get new minX, maxX according to exact start time of days
get_trim_tps_data(c_data, self.min_x, self.max_x,
self.each_day5_min_list)
total_days = c_data['tps_data'].shape[0]
plot_h = self.plotting_axes.get_height(
1.5 * total_days, bw_plots_distance=0.003)
@@ -297,7 +366,7 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
xdata = 287
ydata = round(event.mouseevent.ydata) # y value on the plot
# refer to description in get_plot_data to understand x,y vs
# refer to description in plot_channel to understand x,y vs
# day_index, five_min_index
day_index = - ydata
five_min_index = xdata
@@ -374,6 +443,11 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
for zm2 in self.zoom_marker2s:
zm2.set_data(x_idx, y_idx)
def request_stop(self):
"""Request all running channel processors to stop."""
for processor in self.tps_processors:
processor.request_stop()
class TimePowerSquaredDialog(QtWidgets.QWidget):
def __init__(self, parent):
Loading