Compare commits

..

2 Commits

19 changed files with 234 additions and 116 deletions

View File

@ -81,19 +81,19 @@
],
"distance_l_2": [
0.0275,
0.03,
0.033,
0.033,
0.033,
0.033,
0.033,
0.033,
0.033,
0.033,
0.033,
0.033,
0.033,
0.033
0.0255,
0.0242,
0.0245,
0.0228,
0.0236,
0.0229,
0.0248,
0.024,
0.0235,
0.025,
0.0276,
0.0234,
0.0215
],
"distance_h_end1": [
0.003,

View File

@ -17,4 +17,5 @@ class Controller(BaseController):
self.signal_settings.emit(settings)
def open_custom_file (self, filepath: str) -> None:
self.signal_monitor.emit(filepath)
self.signal_monitor.emit(filepath)

Binary file not shown.

View File

@ -5,6 +5,7 @@ from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from utils.base.base import BaseMainWindow, BaseController
from gui.settings_window import settingsWindow
from gui.reportGui import ReportSettings
class MainWindow(BaseMainWindow):
def __init__(self,
@ -15,8 +16,10 @@ class MainWindow(BaseMainWindow):
self.set_style(self)
self.settings_button.clicked.connect(self._show_settings)
self.select_dir_button.clicked.connect(self._select_dir)
self.report_button.clicked.connect(self._report_window)
self.operSettings = settingsWindow("params/operator_params.json", 'Operator', self._updater_trigger)
self.sysSettings = settingsWindow("params/system_params.json", 'System', self._updater_trigger)
self.repSettings = ReportSettings()
def initUI(self) -> None:
self.tabWidget = QtWidgets.QTabWidget()
@ -29,19 +32,30 @@ class MainWindow(BaseMainWindow):
self.settings_button.setFixedWidth(160)
self.select_dir_button = QtWidgets.QPushButton("Open directory")
self.select_dir_button.setFixedWidth(175)
self.report_button = QtWidgets.QPushButton("Generate report")
self.report_button.setFixedWidth(175)
self.rep_settings_button = QtWidgets.QPushButton("Report settings")
self.rep_settings_button.setFixedWidth(175)
self.rep_settings_button.setVisible(False)
button_layout = QtWidgets.QHBoxLayout()
button_layout.setSpacing(2)
button_layout.addWidget(self.settings_button)
button_layout.addWidget(self.select_dir_button)
button_layout.addWidget(self.report_button)
button_layout.addWidget(self.rep_settings_button)
layout.addLayout(button_layout)
self.setLayout(layout)
def show_plot_tabs(self, plot_widgets: list[QtWidgets.QWidget]) -> None:
for plot_widget in plot_widgets:
widget, reg_items, curve_items = plot_widget
tab = QtWidgets.QWidget()
tab.setProperty("reg_items", reg_items)
tab.setProperty("curve_items", curve_items)
grid = QtWidgets.QGridLayout()
grid.addWidget(plot_widget)
grid.addWidget(widget)
tab.setLayout(grid)
self.tabWidget.addTab(tab, "SF_trace_" + dt.now().strftime('%Y_%m_%d-%H_%M_%S'))
self.tabWidget.setCurrentWidget(tab)
@ -51,7 +65,6 @@ class MainWindow(BaseMainWindow):
for i in range(0, tab_count-2):
self._close_tab(i)
def keyPressEvent(self, a0):
if a0.key() == Qt.Key_F5:
pass
@ -77,5 +90,22 @@ class MainWindow(BaseMainWindow):
if folder_path:
self._controller.open_custom_file(folder_path)
def _report_window(self):
tab = self.tabWidget.currentWidget()
reg_items = tab.property("reg_items")
curve_items = tab.property("curve_items")
print(curve_items)
print(reg_items)
self.rep_settings_button.setVisible(True)
self.rep_settings_button.clicked.connect(lambda:self.repSettings.build(reg_items, curve_items))

View File

@ -1,8 +1,6 @@
import pandas as pd
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QGraphicsRectItem
import copy
import pyqtgraph as pg
from typing import Optional, Any
@ -14,56 +12,6 @@ class ProcessStage():
finish_index:int
class PlotWidget(BasePlotWidget):
def _create_navigator(self,
time_region:tuple[float, float],
main_plot: pg.PlotWidget,
dataframe: pd.DataFrame,
real_signals: list[dict[str, Any]]) -> list[pg.PlotWidget, pg.LinearRegionItem]:
"""
Создаёт график-навигатор, отображающий все данные в уменьшенном масштабе.
"""
navigator = pg.PlotWidget(title="Navigator")
navigator.setFixedHeight(100)
for signal in real_signals:
if signal["name"] in dataframe.columns:
x = dataframe["time"]
y = dataframe[signal["name"]]
x_downsampled, y_downsampled = self._downsample_data(x, y, max_points=1000)
navigator.plot(x_downsampled, y_downsampled, pen=signal["pen"], name=signal["name"])
ROI_region = pg.LinearRegionItem(values=time_region, movable=True, brush=pg.mkBrush(0, 0, 255, 100))
navigator.addItem(ROI_region)
# Связываем изменение региона навигатора с обновлением области просмотра основного графика
ROI_region.sigRegionChanged.connect(lambda: self._sync_main_plot_with_navigator(main_plot, ROI_region))
return navigator, ROI_region
def _downsample_data(self, x, y, max_points=5000):
"""
Понижает разрешение данных до заданного количества точек для улучшения производительности навигатора.
"""
if len(x) > max_points:
factor = len(x) // max_points
x_downsampled = x[::factor]
y_downsampled = y[::factor]
return x_downsampled, y_downsampled
return x, y
def _sync_main_plot_with_navigator(self,
main_plot: pg.PlotWidget,
region: pg.LinearRegionItem):
"""
Синхронизирует область просмотра основного графика с регионом навигатора.
"""
x_min, x_max = region.getRegion()
if main_plot:
main_plot.blockSignals(True)
main_plot.setXRange(x_min, x_max, padding=0)
main_plot.blockSignals(False)
def _create_curve_ideal(self,
signal: dict[str, Any],
@ -111,6 +59,7 @@ class PlotWidget(BasePlotWidget):
plot_widget: pg.PlotWidget,
point_events: dict[str, list[float]],
dataframe_headers: list[str],
reg_items: dict,
transparency: int = 75) -> None:
"""
Добавляет регионы для реальных этапов, если все стадии есть в заголовках датафрейма.
@ -123,11 +72,14 @@ class PlotWidget(BasePlotWidget):
if region is not None:
region.setZValue(-20)
plot_widget.addItem(region)
reg_items["real"].setdefault(stage, [])
reg_items["real"][stage].append(region)
def _add_ideal_stage_regions(self,
plot_widget: pg.PlotWidget,
ideal_data: dict[str, Any],
point_events: dict[str, list[float]],
reg_items: dict,
transparency: int = 125) -> None:
"""
Добавляет регионы для идеальных этапов.
@ -141,12 +93,15 @@ class PlotWidget(BasePlotWidget):
if region:
region.setZValue(-10)
plot_widget.addItem(region)
reg_items["ideal"].setdefault(stage, [])
reg_items["ideal"][stage].append(region)
def _add_ideal_signals(self,
plot_widget: pg.PlotWidget,
ideal_data: dict[str, Any],
point_events: dict[str, list[float]],
ideal_signals: list[dict[str, Any]]) -> None:
ideal_signals: list[dict[str, Any]],
curve_items: dict) -> None:
"""
Добавляет идеальные сигналы для каждого этапа.
"""
@ -161,12 +116,15 @@ class PlotWidget(BasePlotWidget):
if curve:
curve.setZValue(10)
plot_widget.addItem(curve)
curve_items["ideal"].setdefault(signal["name"], {})
curve_items["ideal"][signal["name"]][stage] = curve
def _add_real_signals(self,
plot_widget: pg.PlotWidget,
dataframe: pd.DataFrame,
real_signals: list[dict[str, Any]],
legend: pg.LegendItem) -> None:
legend: pg.LegendItem,
curve_items: dict) -> None:
"""
Добавляет реальные сигналы из dataframe на виджет.
"""
@ -176,6 +134,8 @@ class PlotWidget(BasePlotWidget):
plot = plot_widget.plot(dataframe["time"], dataframe[signal["name"]], pen=signal["pen"], fast=True)
plot.setZValue(0)
legend.addItem(plot, signal["name"])
curve_items["real"].setdefault(signal["name"], {})
curve_items["real"][signal["name"]] = plot
def _add_performance_label(self,
layout: QVBoxLayout,
@ -197,29 +157,6 @@ class PlotWidget(BasePlotWidget):
layout.addWidget(performance_label)
performance_label.update()
def _mirror_shift_data(self,
valid_str: str,
signals: list[dict],
dataframe: pd.DataFrame,
shift: float) -> pd.DataFrame:
keys = dataframe.keys()
for signal in signals:
if valid_str in signal["name"] and signal["name"] in keys:
dataframe[signal["name"]] = dataframe[signal["name"]].apply(lambda x: shift-x)
return dataframe
def _shift_data(self,
valid_str: str,
signals: list[dict],
dataframe: pd.DataFrame,
shift: float) -> pd.DataFrame:
keys = dataframe.keys()
for signal in signals:
if valid_str in signal["name"] and signal["name"] in keys:
dataframe[signal["name"]] = dataframe[signal["name"]].apply(lambda x: x + shift)
return dataframe
def _build_widget(self, data: list[Any]) -> QWidget:
"""
Собирает графический виджет для одного набора данных.
@ -227,6 +164,8 @@ class PlotWidget(BasePlotWidget):
"""
widget = QWidget()
layout = QVBoxLayout(widget)
reg_items = {"real":{}, "ideal":{}}
curve_items = {"real":{}, "ideal":{}}
dataframe, points_pocket, useful_data = data
tesla_time = useful_data["tesla_time"]
@ -267,7 +206,7 @@ class PlotWidget(BasePlotWidget):
# Добавляем реальные стадии
if settings["stages"]:
self._add_stage_regions(plot_widget, point_events, dataframe_headers, 75)
self._add_stage_regions(plot_widget, point_events, dataframe_headers, reg_items, 75)
# TODO: подобрать не вырвеглазные цвета, возможно ограничить зону
if settings["workpiece"]:
@ -284,8 +223,8 @@ class PlotWidget(BasePlotWidget):
# Добавляем идеальные стадии и идеальные сигналы
if settings["ideals"]:
self._add_ideal_stage_regions(plot_widget, ideal_data, point_events, 100)
self._add_ideal_signals(plot_widget, ideal_data, point_events, description["Ideal_signals"])
self._add_ideal_stage_regions(plot_widget, ideal_data, point_events, reg_items, 100)
self._add_ideal_signals(plot_widget, ideal_data, point_events, description["Ideal_signals"], curve_items)
# Подсчёт производительности
if settings["performance"]:
@ -304,7 +243,7 @@ class PlotWidget(BasePlotWidget):
worst_timeframe = point_timeframe
# Добавляем реальные сигналы
self._add_real_signals(plot_widget, dataframe, description["Real_signals"], legend)
self._add_real_signals(plot_widget, dataframe, description["Real_signals"], legend, curve_items)
if widget_num == 0:
main_plot = plot_widget
else:
@ -323,17 +262,7 @@ class PlotWidget(BasePlotWidget):
main_plot.sigXRangeChanged.connect(lambda _, plot=main_plot, region=ROI_region: self._sync_navigator_with_main(main_plot=plot, region=region))
widget.setLayout(layout)
return widget
def _sync_navigator_with_main(self, main_plot: pg.PlotWidget, region:pg.LinearRegionItem):
"""
Синхронизирует регион навигатора с областью просмотра основного графика.
"""
if region:
x_min, x_max = main_plot
region.blockSignals(True) # Предотвращаем рекурсию
region.setRegion([x_min, x_max])
region.blockSignals(False)
return widget, reg_items, curve_items
def build(self, data: list[list[Any]]) -> None:
"""
@ -345,7 +274,8 @@ class PlotWidget(BasePlotWidget):
...
]
"""
widgets = [self._build_widget(data_sample) for data_sample in data]
self._mediator.notify(self, widgets)
widgets_datapack = [self._build_widget(data_sample) for data_sample in data]
self._mediator.notify(self, widgets_datapack)

77
src/gui/reportGui.py Normal file
View File

@ -0,0 +1,77 @@
import pyqtgraph as pg
from pyqtgraph.parametertree import Parameter, ParameterTree
from typing import Union
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
class ReportSettings(QtWidgets.QWidget):
def build(self, reg_items: dict, curve_items: dict) -> None:
param_tree = ParameterTree()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(param_tree)
self.setLayout(layout)
body= [
self._generate_reg_params(reg_items),
self._generate_curve_params(curve_items)
]
# Добавляем параметры в дерево
params = Parameter.create(name='params', type='group', children=body)
param_tree.setParameters(params, showTop=False)
self.show()
def _generate_reg_params(self, reg_items: dict) -> dict:
res = {'name': 'Sectors', 'type': 'group', 'children': [
{'name': 'Real sectors', 'type': 'group', 'children': self._create_samples(reg_items["real"])},
{'name': 'Ideal sectors', 'type': 'group', 'children': self._create_samples(reg_items["ideal"])},
]}
return res
def _generate_curve_params(self, curve_items: dict) -> dict:
res = {'name': 'Plots', 'type': 'group', 'children': [
{'name': 'Real plots', 'type': 'group', 'children': self._create_samples(curve_items["real"])},
{'name': 'Ideal plots', 'type': 'group', 'children': self._create_ideal_curves(curve_items["ideal"])},
]}
return res
def _create_ideal_curves(self, curve: dict) -> list[dict]:
"""Создаем секторы с этапами циклограммы"""
res = []
for key, item in curve.items():
param = {'name': key, 'type': 'group', 'children': self._create_samples(item)}
res.append(param)
return res
def _create_samples(self, sector: dict) -> list[dict]:
res = []
for key, item in sector.items():
sample = item[0] if type(item) == list else item
param = {'name': key, 'type': 'group', 'children': self._create_settings(sample)}
res.append(param)
return res
def _create_settings(self, item: Union[pg.LinearRegionItem, pg.PlotDataItem]) -> list[dict]:
"""Настройки для элемента"""
if type(item) == pg.LinearRegionItem:
pen = item.lines[0].pen
brush = item.brush
fill_color = brush.color().getRgb()
else:
pen = pg.mkPen(item.opts.get("pen"))
fill_color = None
line_color = pen.color().getRgb()
line_thickness = pen.width()
visibility = item.isVisible()
return [
{'name': 'Line color', 'type': 'color', 'value': line_color},
{'name': 'Line thickness', 'type': 'int', 'value': line_thickness, 'limits': (1, 10)},
{'name': 'Visibility', 'type': 'bool', 'value': visibility},
{'name': 'Fill color', 'type': 'color', 'value': fill_color},
]

View File

@ -9,10 +9,8 @@ from PyQt5.QtCore import QThread, QObject, QTimer
from PyQt5.QtWidgets import QWidget, QTabWidget
from PyQt5.QtOpenGL import QGLWidget
from OptAlgorithm import OptAlgorithm
import pandas as pd
import pandas as pd
import numpy as np
import pyqtgraph as pg
@ -125,7 +123,7 @@ class BasePlotWidget:
"zoom": False,
"stages": True,
"performance": True,
"ideals": True,
"ideals": False,
"mirror ME": False,
"workpiece": False,
"force compensation FE": False
@ -156,7 +154,7 @@ class BasePlotWidget:
"zoom": False,
"stages": True,
"performance": False,
"ideals": True,
"ideals": False,
"mirror ME": True,
"workpiece": True,
"force compensation FE": True
@ -227,6 +225,88 @@ class BasePlotWidget:
font-family: "Segoe UI", sans-serif;
}""")
def _downsample_data(self, x, y, max_points=5000):
"""
Понижает разрешение данных до заданного количества точек для улучшения производительности навигатора.
"""
if len(x) > max_points:
factor = len(x) // max_points
x_downsampled = x[::factor]
y_downsampled = y[::factor]
return x_downsampled, y_downsampled
return x, y
def _create_navigator(self,
time_region:tuple[float, float],
main_plot: pg.PlotWidget,
dataframe: pd.DataFrame,
real_signals: list[dict[str, Any]]) -> list[pg.PlotWidget, pg.LinearRegionItem]:
"""
Создаёт график-навигатор, отображающий все данные в уменьшенном масштабе.
"""
navigator = pg.PlotWidget(title="Navigator")
navigator.setFixedHeight(100)
for signal in real_signals:
if signal["name"] in dataframe.columns:
x = dataframe["time"]
y = dataframe[signal["name"]]
x_downsampled, y_downsampled = self._downsample_data(x, y, max_points=1000)
navigator.plot(x_downsampled, y_downsampled, pen=signal["pen"], name=signal["name"])
ROI_region = pg.LinearRegionItem(values=time_region, movable=True, brush=pg.mkBrush(0, 0, 255, 100))
navigator.addItem(ROI_region)
# Связываем изменение региона навигатора с обновлением области просмотра основного графика
ROI_region.sigRegionChanged.connect(lambda: self._sync_main_plot_with_navigator(main_plot, ROI_region))
return navigator, ROI_region
def _sync_main_plot_with_navigator(self,
main_plot: pg.PlotWidget,
region: pg.LinearRegionItem):
"""
Синхронизирует область просмотра основного графика с регионом навигатора.
"""
x_min, x_max = region.getRegion()
if main_plot:
main_plot.blockSignals(True)
main_plot.setXRange(x_min, x_max, padding=0)
main_plot.blockSignals(False)
def _mirror_shift_data(self,
valid_str: str,
signals: list[dict],
dataframe: pd.DataFrame,
shift: float) -> pd.DataFrame:
keys = dataframe.keys()
for signal in signals:
if valid_str in signal["name"] and signal["name"] in keys:
dataframe[signal["name"]] = dataframe[signal["name"]].apply(lambda x: shift-x)
return dataframe
def _shift_data(self,
valid_str: str,
signals: list[dict],
dataframe: pd.DataFrame,
shift: float) -> pd.DataFrame:
keys = dataframe.keys()
for signal in signals:
if valid_str in signal["name"] and signal["name"] in keys:
dataframe[signal["name"]] = dataframe[signal["name"]].apply(lambda x: x + shift)
return dataframe
def _sync_navigator_with_main(self, main_plot: pg.PlotWidget, region:pg.LinearRegionItem):
"""
Синхронизирует регион навигатора с областью просмотра основного графика.
"""
if region:
x_min, x_max = main_plot
region.blockSignals(True) # Предотвращаем рекурсию
region.setRegion([x_min, x_max])
region.blockSignals(False)
@property
def mediator(self) -> BaseMediator:
return self._mediator