From 7b759a86d24afc1b68e363dda17a3931217e537e Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 5 Feb 2025 18:52:04 +0300 Subject: [PATCH] =?UTF-8?q?dev:=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=20PassportFormer=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BA=D0=BB=D0=B8=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D1=81=D0=BA=D0=B8=D1=85=20=D1=82=D1=80=D0=B5=D0=B9?= =?UTF-8?q?=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/base.py | 74 ++++++--- src/controller/mediator.py | 3 +- src/controller/passport_former.py | 49 ++++-- src/gui/start_widget.py | 3 +- src/performance/roboter.py | 140 +++++++++++------- .../point_40000_2025_01_31-14_21_43.csv | 2 +- 6 files changed, 182 insertions(+), 89 deletions(-) diff --git a/src/base/base.py b/src/base/base.py index 29b5c2e..1676103 100644 --- a/src/base/base.py +++ b/src/base/base.py @@ -1,7 +1,7 @@ from __future__ import annotations import os from typing import Optional, Union -from dataclasses import dataclass +from dataclasses import dataclass, field from cachetools import LRUCache import pandas as pd @@ -36,44 +36,46 @@ class KukaTXT: @dataclass class KukaDataHead: - rob_ID: int - filename: str - channels: dict + rob_ID: int = 0 + filename: str = "" + channels: dict = field(default_factory= lambda: {}) @dataclass class PlotItems: - regions: dict - curves: dict - qt_items: dict + regions: dict = field(default_factory= lambda: {}) + curves: dict = field(default_factory= lambda: {}) + qt_items: dict = field(default_factory= lambda: {}) @dataclass class PointPassport: - timeframe: list - events: dict - ideal_data: dict - useful_data: dict + timeframe: list = field(default_factory= lambda: []) + events: dict = field(default_factory= lambda: {}) + ideal_data: dict = field(default_factory= lambda: {}) + useful_data: dict = field(default_factory= lambda: {}) @dataclass class UsefulGraphData: - client_time: float - range_ME: float - k_hardness: float + client_time: float = 0 + range_ME: float = 0 + k_hardness: float = 0 @dataclass class GraphicPassport: - dataframe: pd.DataFrame - points_pocket: list[PointPassport] - useful_data: UsefulGraphData + dataframe: pd.DataFrame = pd.DataFrame({}) + points_pocket: list[PointPassport] = field(default_factory= lambda: []) + useful_data: UsefulGraphData = UsefulGraphData() @dataclass class Settings: - operator: dict - system: dict + operator: dict = field(default_factory= lambda: {}) + system: dict = field(default_factory= lambda: {}) + filter: dict = field(default_factory= lambda: {}) + class BaseMediator: @@ -522,7 +524,10 @@ class BasePointPassportFormer: "k_prop", "time_capture"] - def form_passports(self) -> list[GraphicPassport]: + def form_passports(self, data: list[pd.DataFrame]) -> None: + ... + + def form_customer_passport(self, data: list[pd.DataFrame, dict]) -> None: ... def update_settings(self, params: list) -> None: @@ -549,16 +554,24 @@ class BaseRawTraceProcessor: def __init__(self, dataparser:BaseKukaDataParser, - textparser:BaseKukaTextParser, + textparser:BaseKukaTextParser, + data_detector:BaseTraceStageDetector, + text_detector:BaseTextStageDetector, mediator:Optional[BaseMediator] = None): self._mediator = mediator self._dataparser = dataparser self._textparser = textparser + self._data_detector = data_detector + self._text_detector = text_detector self._trace_df = None self._text_data = None + def prerender(self, data:list[str]) -> None: ... + + def update_settings(self, data:Settings) -> None: + ... @property def mediator(self) -> BaseMediator: @@ -586,3 +599,22 @@ class BaseKukaTextParser: def parse(self, path:str ) -> list[KukaTXT]: ... + + + +class BaseTraceStageDetector: + + def __init__(self, parent:BaseRawTraceProcessor = None): + self._parent = parent + + def detect_stages(self, df: pd.DataFrame) -> list: + ... + + +class BaseTextStageDetector: + + def __init__(self, parent:BaseRawTraceProcessor = None): + self._parent = parent + + def detect_welding(self, data:list[KukaTXT]) -> list: + ... \ No newline at end of file diff --git a/src/controller/mediator.py b/src/controller/mediator.py index 591c233..dee74b4 100644 --- a/src/controller/mediator.py +++ b/src/controller/mediator.py @@ -41,9 +41,10 @@ class Mediator(BaseMediator): if issubclass(source.__class__, BaseController): self._file_manager.update_monitor_settings(data) self._passport_former.update_settings(data) + self._trace_processor.update_settings(data) if issubclass(source.__class__, BaseRawTraceProcessor): - self._plot.build_raw_trace(data) + self._passport_former.form_customer_passport(data) def prerender(self, data:list[str]) -> None: self._trace_processor.prerender(data) diff --git a/src/controller/passport_former.py b/src/controller/passport_former.py index 0bc0b2a..8d98905 100644 --- a/src/controller/passport_former.py +++ b/src/controller/passport_former.py @@ -11,9 +11,9 @@ from base.base import BasePointPassportFormer, BaseIdealDataBuilder, PointPasspo class PassportFormer(BasePointPassportFormer): - def form_passports(self, data: list[pd.DataFrame]) -> list[GraphicPassport]: + def form_passports(self, data: list[pd.DataFrame]) -> None: try: - return_data = [self._build_graphic_passport(dataframe) for dataframe in data] + return_data = [self._build_from_df_only(dataframe) for dataframe in data] except: # TODO: обработка исключений!!! tb = sys.exc_info()[2] @@ -27,6 +27,24 @@ class PassportFormer(BasePointPassportFormer): def update_settings(self, settings: Settings): self._settings = settings + + def form_customer_passport(self, data: list[pd.DataFrame, dict]) -> None: + try: + dataframe, events = data + point_quantity = len(events["Welding"][0]) + data = self._form_graphic_passport(dataframe, events, point_quantity) + return_data = [data] + except: + #TODO: Добавить обработку исключений + tb = sys.exc_info()[2] + tbinfo = traceback.format_tb(tb)[0] + pymsg = "Traceback info:\n" + tbinfo + "\nError Info:\n" + str(sys.exc_info()[1]) + logger.error(pymsg) + return_data = [] + finally: + self._mediator.notify(self, return_data) + + @staticmethod def _find_indexes(signal: str, @@ -36,11 +54,11 @@ class PassportFormer(BasePointPassportFormer): finish_idx = np.where(stage_diff == -1) return start_idx[0], finish_idx[0] - def _find_events(self, - signal: str, + @staticmethod + def _find_events(signal: str, times:pd.Series, dataframe: pd.DataFrame) -> tuple[list[float], list[float]]: - start_idx, finish_idx = self._find_indexes(signal, dataframe) + start_idx, finish_idx = PassportFormer._find_indexes(signal, dataframe) if len(start_idx) > 0 and len(finish_idx) > 0 and start_idx[0] > finish_idx[0]: start_idx = np.insert(start_idx, 0, 0) @@ -51,7 +69,7 @@ class PassportFormer(BasePointPassportFormer): return start_list, end_list - def _filter_events(self, + def _generate_events(self, times: pd.Series, dataframe: pd.DataFrame) -> tuple[dict[str, list[list[float]]], int]: events = {} @@ -104,10 +122,13 @@ class PassportFormer(BasePointPassportFormer): if str(key) in self._OptAlgorithm_system_params) return (operator_tuple, system_tuple) - def _build_graphic_passport(self, dataframe: pd.DataFrame) -> GraphicPassport: + def _build_from_df_only(self, dataframe: pd.DataFrame) -> GraphicPassport: - if dataframe is not None: - events, point_quantity = self._filter_events(dataframe["time"], dataframe) + if (dataframe is not None and + set(self._stages).issubset(set(dataframe.columns.tolist()))): + events, point_quantity = self._generate_events(dataframe["time"], dataframe) + point_quantity = len(events["Welding"]) + pass if point_quantity == 0: return [] else: @@ -115,6 +136,14 @@ class PassportFormer(BasePointPassportFormer): key = list(self._settings.operator.keys())[0] point_quantity = len(self._settings.operator[key]) + passport = self._form_graphic_passport(dataframe, events, point_quantity) + return passport + + def _form_graphic_passport(self, + dataframe:pd.DataFrame, + events:dict, + point_quantity:int) -> GraphicPassport: + system_settings = {key: value[0] for key, value in self._settings.system.items()} graphic_passport = GraphicPassport(dataframe, [], self._form_graphic_useful_data(system_settings)) @@ -158,7 +187,7 @@ class PassportFormer(BasePointPassportFormer): } return operator_settings - def _form_point_events(self, events:dict, idx) -> list: + def _form_point_events(self, events:dict, idx:int) -> list[list, dict]: timeframe, point_events = None, None if events is not None: idx_shift = idx+1 if events[self._stages[-1]][0][0] == 0 else idx diff --git a/src/gui/start_widget.py b/src/gui/start_widget.py index f627172..e2ce785 100644 --- a/src/gui/start_widget.py +++ b/src/gui/start_widget.py @@ -329,4 +329,5 @@ class ClientAnalyzerWidget(QWidget): main_layout.addWidget(self._tabWidget) main_layout.addWidget(button_widget) - self.setLayout(main_layout) \ No newline at end of file + self.setLayout(main_layout) + \ No newline at end of file diff --git a/src/performance/roboter.py b/src/performance/roboter.py index 72c3d2b..a22f05c 100644 --- a/src/performance/roboter.py +++ b/src/performance/roboter.py @@ -6,7 +6,10 @@ from dataclasses import dataclass, field import numpy as np import pandas as pd -from base.base import BaseKukaDataParser, BaseKukaTextParser, BaseRawTraceProcessor, KukaDataHead, KukaTXT +from base.base import (BaseKukaDataParser, BaseKukaTextParser, + BaseTraceStageDetector, BaseTextStageDetector, + BaseRawTraceProcessor, KukaDataHead, + KukaTXT, Settings) class KukaDataParser(BaseKukaDataParser): @@ -64,7 +67,8 @@ class KukaDataParser(BaseKukaDataParser): except ValueError as e: raise ValueError(f"Ошибка при изменении формы data_array: {e}") - return pd.DataFrame(axes*mul, columns=axes_names, index=time_axis) + dataframe = pd.concat([time_axis, pd.DataFrame(axes*mul, columns=axes_names)], axis=1) + return dataframe @staticmethod def _extract_channels_data(head: KukaDataHead) -> Tuple[np.ndarray, list]: @@ -107,7 +111,6 @@ class KukaTextParser(BaseKukaTextParser): datapack = self._check_msg_flow(line, datapack) return self._datapacks - def _check_msg_flow(self, line:str, datapack:KukaTXT) -> KukaTXT: if line == "#BEGINMOTIONINFO": self._in_msg = True @@ -164,94 +167,76 @@ class KukaTextParser(BaseKukaTextParser): return datapack -class TraceStageDetector: +class TraceStageDetector(BaseTraceStageDetector): - def __init__( - self, - rob_vel_zero_th: float = 0.008, - act_vel_zero_th: float = 10, - act_vel_touching: float = 2050, - act_vel_touching_th: float = 200, - act_curr_force_th: float = 0.5, - region_of_focus: list = [0, 100] - ): - - self.rob_vel_zero_th = rob_vel_zero_th - self.act_vel_zero_th = act_vel_zero_th - self.act_vel_touching = act_vel_touching - self.act_vel_touching_th = act_vel_touching_th - self.act_curr_force_th = act_curr_force_th - self.ROF = region_of_focus + def __init__(self, parent:TraceProcessor = None): + super().__init__(parent) def detect_stages(self, df: pd.DataFrame) -> list: df = df.sort_index() stage_markers = [] - df_indices = df.index.to_list() # времена (в секундах) как список + timestamps = df['time'].to_list() # времена (в секундах) как список - for i in df_indices: - mark = self._mark_timestamp(df, i) + for i, time in enumerate(timestamps): + mark = self._mark_timestamp(df, i, time) if mark: stage_markers.append(mark) # Объединяем подряд идущие одинаковые метки в единые этапы. - stages = self._merge_marks_to_stages(stage_markers, df_indices) + stages = self._merge_marks_to_stages(stage_markers, timestamps) return stages - def _mark_timestamp(self, df:pd.DataFrame, index:float) -> Union[str, None]: + def _mark_timestamp(self, df:pd.DataFrame, index:int, time:float) -> Union[str, None]: # Не интересные значения помечаем как "unknown" - if index < self.ROF[0] or index > self.ROF[1]: + if (time < self._parent._settings.filter["ROI_start"][0] or + time > self._parent._settings.filter["ROI_finish"][0]): return "unknown" rob_vel = df.loc[index, "CartVel_Act"] act_vel = df.loc[index, "DriveMotorVel_Act7"] act_curr = df.loc[index, "DriveMotorCurr_Act7"] - # 1) Перемещение (moving): если скорость |CartVel_Act| > vel_threshold - if abs(rob_vel) > self.rob_vel_zero_th: - return "moving" + if abs(rob_vel) > self._parent._settings.filter["robot_zero_velocity_trashold"][0]: + return "Oncomming" - # 2) Создание усилия (force): если |DriveMotorVel_Act7| < act_vel_zero_th и |DriveMotorCurr_Act7| > act_curr_force_th - if abs(act_vel) < self.act_vel_zero_th and abs(act_curr) > self.act_curr_force_th: - return "force" + if (abs(act_vel) < self._parent._settings.filter["actuator_zero_velocity_trashold"][0] and + abs(act_curr) > self._parent._settings.filter["actuator_current_trashold"][0]): + return "Squeeze&Welding&Relief" - # 3) Смыкание (closing): если скорость |DriveMotorVel_Act7| = act_vel_touching - if self.act_vel_touching_th > abs(abs(act_vel) - self.act_vel_touching): - return "closing" + if self._parent._settings.filter["actuator_finishing_velocity_trashold"][0] > abs(abs(act_vel) - self._parent._settings.filter["actuator_finishing_velocity"][0]): + return "Closing" return None - - def _merge_marks_to_stages(self, stage_markers:list, df_indices:list) -> list: + def _merge_marks_to_stages(self, stage_markers:list, timestamps:list) -> list: stages = [] current_stage = stage_markers[0] - start_time = df_indices[0] + start_time = timestamps[0] for i in range(1, len(stage_markers)): if stage_markers[i] != current_stage: - end_time = df_indices[i - 1] + end_time = timestamps[i - 1] stages.append({ "stage": current_stage, "start_time": start_time, "end_time": end_time }) current_stage = stage_markers[i] - start_time = df_indices[i] + start_time = timestamps[i] stages.append({ "stage": current_stage, "start_time": start_time, - "end_time": df_indices[-1] + "end_time": timestamps[-1] }) return stages -class TextStageDetector: +class TextStageDetector(BaseTextStageDetector): - def __init__(self, - region_of_focus: list = [0, 100] - ): - self.ROF = region_of_focus + def __init__(self, parent :TraceProcessor = None): + super().__init__(parent) - def detect_welding(self, data:list[KukaTXT]): + def detect_welding(self, data:list[KukaTXT]) -> list: stages = [] for i in range(len(data)): if data[i].func == " SPOT" and data[i].signal == " END": @@ -266,26 +251,34 @@ class TextStageDetector: class TraceProcessor(BaseRawTraceProcessor): def __init__(self): + self._settings = Settings() dataparser = KukaDataParser() textparser = KukaTextParser() - super().__init__(dataparser, textparser) + data_detector = TraceStageDetector(self) + text_detector = TextStageDetector(self) + + super().__init__(dataparser, textparser, data_detector, text_detector) def prerender(self, data:list[str]) -> None: - if data: - dat_filepath = data[0] - txt_filepath = None - elif len(data) == 2: + if data and len(data) == 2: dat_filepath = data[0] txt_filepath = data[1] + elif data: + dat_filepath = data[0] + txt_filepath = None else: dat_filepath = None txt_filepath = None - self._trace_df = self._unpack_trace(dat_filepath) - self._text_data = self._unpack_text(txt_filepath) + trace_df = self._unpack_trace(dat_filepath) + text_data = self._unpack_text(txt_filepath) + events = self._detect_stages(trace_df, text_data) + print (events) - self._mediator.notify(self, self._trace_df) + self._mediator.notify(self, [trace_df, events]) + def update_settings(self, data:Settings) -> None: + self._settings = data def _unpack_trace(self, dat_filepath:str = None) -> Optional[pd.DataFrame]: if dat_filepath: @@ -296,8 +289,45 @@ class TraceProcessor(BaseRawTraceProcessor): if txt_filepath: return self._textparser.parse(txt_filepath) return None + + def _detect_stages(self, trace_df:pd.DataFrame, text_data:list) -> dict[list]: + if trace_df is not None and text_data is not None: + trace_stages = self._data_detector.detect_stages(trace_df) + welding_stages = self._text_detector.detect_welding(text_data) + events = self._form_events(trace_stages, welding_stages) + return events + return None + @staticmethod + def _form_events(trace_stages:list, welding_stages:list) -> dict[list]: + idx = 0 + events = { + "Closing":[[],[]], + "Squeeze":[[],[]], + "Welding":[[],[]], + "Relief":[[],[]], + "Oncomming":[[],[]] + } + for i in range(len(trace_stages)-1): + if trace_stages[i]['stage'] == 'Squeeze&Welding&Relief': + if trace_stages[i]['end_time'] > welding_stages[idx]['start_time']: + + events["Squeeze"][0].append(trace_stages[i]['start_time']) + events["Squeeze"][1].append(welding_stages[idx]['start_time']) + events["Welding"][0].append(welding_stages[idx]['start_time']) + events["Welding"][1].append(welding_stages[idx]['end_time']) + + events["Relief"][0].append(welding_stages[idx]['end_time']) + events["Relief"][1].append(trace_stages[i+1]['start_time']) + idx += 1 + else: + events["Squeeze"][0].append(trace_stages[i]['start_time']) + events["Squeeze"][1].append(trace_stages[i]['end_time']) + else: + events[trace_stages[i]['stage']][0].append(trace_stages[i]['start_time']) + events[trace_stages[i]['stage']][1].append(trace_stages[i]['end_time']) + return events "Перемещение" diff --git a/trace_samples/point_40000_2025_01_31-14_21_43.csv b/trace_samples/point_40000_2025_01_31-14_21_43.csv index ac0ed60..ae03b47 100644 --- a/trace_samples/point_40000_2025_01_31-14_21_43.csv +++ b/trace_samples/point_40000_2025_01_31-14_21_43.csv @@ -1,4 +1,4 @@ -TIme,Closing,"Electrode Force, N FE","Electrode Force, N ME",Force Control FE,Force Control ME,Hold Position ME,Oncomming,Position Control FE,Position Control ME,Relief,"Rotor Position, mm FE","Rotor Position, mm ME","Rotor Speed, mm/s FE","Rotor Speed, mm/s ME",Squeeze,Welding,Welding Current ME,Welding Voltage ME +time,Closing,"Electrode Force, N FE","Electrode Force, N ME",Force Control FE,Force Control ME,Hold Position ME,Oncomming,Position Control FE,Position Control ME,Relief,"Rotor Position, mm FE","Rotor Position, mm ME","Rotor Speed, mm/s FE","Rotor Speed, mm/s ME",Squeeze,Welding,Welding Current ME,Welding Voltage ME 0.0,False,-382.92999267578125,-253.056396484375,False,False,False,True,False,False,False,10.000015258789062,39.999847412109375,0.01803657039999962,0.012878786772489548,False,False,859.0,0.10200000554323196 0.004000000189989805,False,-367.17999267578125,-264.21148681640625,False,False,False,True,False,False,False,9.999992370605469,39.999847412109375,-0.0009994201827794313,-0.020553337410092354,False,False,857.0,0.10300000756978989 0.00800000037997961,False,-372.42999267578125,-302.747314453125,False,False,False,True,False,False,False,9.999984741210938,39.9998779296875,-0.040557608008384705,0.004086061846464872,False,False,857.0,0.10400000214576721