dev: создан новый метод PassportFormer для обработки клиентских трейсов

This commit is contained in:
Andrew 2025-02-05 18:52:04 +03:00
parent 21fd64d7f7
commit 7b759a86d2
6 changed files with 182 additions and 89 deletions

View File

@ -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:
@ -550,16 +555,24 @@ class BaseRawTraceProcessor:
def __init__(self,
dataparser:BaseKukaDataParser,
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:
return self._mediator
@ -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:
...

View File

@ -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)

View File

@ -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]
@ -28,6 +28,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,
dataframe: pd.DataFrame) -> tuple[np.ndarray, np.ndarray]:
@ -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

View File

@ -330,3 +330,4 @@ class ClientAnalyzerWidget(QWidget):
main_layout.addWidget(self._tabWidget)
main_layout.addWidget(button_widget)
self.setLayout(main_layout)

View File

@ -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:
@ -297,7 +290,44 @@ class TraceProcessor(BaseRawTraceProcessor):
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
"Перемещение"

View File

@ -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

Can't render this file because it is too large.