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 from __future__ import annotations
import os import os
from typing import Optional, Union from typing import Optional, Union
from dataclasses import dataclass from dataclasses import dataclass, field
from cachetools import LRUCache from cachetools import LRUCache
import pandas as pd import pandas as pd
@ -36,44 +36,46 @@ class KukaTXT:
@dataclass @dataclass
class KukaDataHead: class KukaDataHead:
rob_ID: int rob_ID: int = 0
filename: str filename: str = ""
channels: dict channels: dict = field(default_factory= lambda: {})
@dataclass @dataclass
class PlotItems: class PlotItems:
regions: dict regions: dict = field(default_factory= lambda: {})
curves: dict curves: dict = field(default_factory= lambda: {})
qt_items: dict qt_items: dict = field(default_factory= lambda: {})
@dataclass @dataclass
class PointPassport: class PointPassport:
timeframe: list timeframe: list = field(default_factory= lambda: [])
events: dict events: dict = field(default_factory= lambda: {})
ideal_data: dict ideal_data: dict = field(default_factory= lambda: {})
useful_data: dict useful_data: dict = field(default_factory= lambda: {})
@dataclass @dataclass
class UsefulGraphData: class UsefulGraphData:
client_time: float client_time: float = 0
range_ME: float range_ME: float = 0
k_hardness: float k_hardness: float = 0
@dataclass @dataclass
class GraphicPassport: class GraphicPassport:
dataframe: pd.DataFrame dataframe: pd.DataFrame = pd.DataFrame({})
points_pocket: list[PointPassport] points_pocket: list[PointPassport] = field(default_factory= lambda: [])
useful_data: UsefulGraphData useful_data: UsefulGraphData = UsefulGraphData()
@dataclass @dataclass
class Settings: class Settings:
operator: dict operator: dict = field(default_factory= lambda: {})
system: dict system: dict = field(default_factory= lambda: {})
filter: dict = field(default_factory= lambda: {})
class BaseMediator: class BaseMediator:
@ -522,7 +524,10 @@ class BasePointPassportFormer:
"k_prop", "k_prop",
"time_capture"] "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: def update_settings(self, params: list) -> None:
@ -550,16 +555,24 @@ class BaseRawTraceProcessor:
def __init__(self, def __init__(self,
dataparser:BaseKukaDataParser, dataparser:BaseKukaDataParser,
textparser:BaseKukaTextParser, textparser:BaseKukaTextParser,
data_detector:BaseTraceStageDetector,
text_detector:BaseTextStageDetector,
mediator:Optional[BaseMediator] = None): mediator:Optional[BaseMediator] = None):
self._mediator = mediator self._mediator = mediator
self._dataparser = dataparser self._dataparser = dataparser
self._textparser = textparser self._textparser = textparser
self._data_detector = data_detector
self._text_detector = text_detector
self._trace_df = None self._trace_df = None
self._text_data = None self._text_data = None
def prerender(self, data:list[str]) -> None: def prerender(self, data:list[str]) -> None:
... ...
def update_settings(self, data:Settings) -> None:
...
@property @property
def mediator(self) -> BaseMediator: def mediator(self) -> BaseMediator:
return self._mediator return self._mediator
@ -586,3 +599,22 @@ class BaseKukaTextParser:
def parse(self, path:str ) -> list[KukaTXT]: 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): if issubclass(source.__class__, BaseController):
self._file_manager.update_monitor_settings(data) self._file_manager.update_monitor_settings(data)
self._passport_former.update_settings(data) self._passport_former.update_settings(data)
self._trace_processor.update_settings(data)
if issubclass(source.__class__, BaseRawTraceProcessor): 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: def prerender(self, data:list[str]) -> None:
self._trace_processor.prerender(data) self._trace_processor.prerender(data)

View File

@ -11,9 +11,9 @@ from base.base import BasePointPassportFormer, BaseIdealDataBuilder, PointPasspo
class PassportFormer(BasePointPassportFormer): class PassportFormer(BasePointPassportFormer):
def form_passports(self, data: list[pd.DataFrame]) -> list[GraphicPassport]: def form_passports(self, data: list[pd.DataFrame]) -> None:
try: 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: except:
# TODO: обработка исключений!!! # TODO: обработка исключений!!!
tb = sys.exc_info()[2] tb = sys.exc_info()[2]
@ -28,6 +28,24 @@ class PassportFormer(BasePointPassportFormer):
def update_settings(self, settings: Settings): def update_settings(self, settings: 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 @staticmethod
def _find_indexes(signal: str, def _find_indexes(signal: str,
dataframe: pd.DataFrame) -> tuple[np.ndarray, np.ndarray]: dataframe: pd.DataFrame) -> tuple[np.ndarray, np.ndarray]:
@ -36,11 +54,11 @@ class PassportFormer(BasePointPassportFormer):
finish_idx = np.where(stage_diff == -1) finish_idx = np.where(stage_diff == -1)
return start_idx[0], finish_idx[0] return start_idx[0], finish_idx[0]
def _find_events(self, @staticmethod
signal: str, def _find_events(signal: str,
times:pd.Series, times:pd.Series,
dataframe: pd.DataFrame) -> tuple[list[float], list[float]]: 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]: 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) start_idx = np.insert(start_idx, 0, 0)
@ -51,7 +69,7 @@ class PassportFormer(BasePointPassportFormer):
return start_list, end_list return start_list, end_list
def _filter_events(self, def _generate_events(self,
times: pd.Series, times: pd.Series,
dataframe: pd.DataFrame) -> tuple[dict[str, list[list[float]]], int]: dataframe: pd.DataFrame) -> tuple[dict[str, list[list[float]]], int]:
events = {} events = {}
@ -104,10 +122,13 @@ class PassportFormer(BasePointPassportFormer):
if str(key) in self._OptAlgorithm_system_params) if str(key) in self._OptAlgorithm_system_params)
return (operator_tuple, system_tuple) 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: if (dataframe is not None and
events, point_quantity = self._filter_events(dataframe["time"], dataframe) 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: if point_quantity == 0:
return [] return []
else: else:
@ -115,6 +136,14 @@ class PassportFormer(BasePointPassportFormer):
key = list(self._settings.operator.keys())[0] key = list(self._settings.operator.keys())[0]
point_quantity = len(self._settings.operator[key]) 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()} system_settings = {key: value[0] for key, value in self._settings.system.items()}
graphic_passport = GraphicPassport(dataframe, [], self._form_graphic_useful_data(system_settings)) graphic_passport = GraphicPassport(dataframe, [], self._form_graphic_useful_data(system_settings))
@ -158,7 +187,7 @@ class PassportFormer(BasePointPassportFormer):
} }
return operator_settings 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 timeframe, point_events = None, None
if events is not None: if events is not None:
idx_shift = idx+1 if events[self._stages[-1]][0][0] == 0 else idx 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(self._tabWidget)
main_layout.addWidget(button_widget) main_layout.addWidget(button_widget)
self.setLayout(main_layout) self.setLayout(main_layout)

View File

@ -6,7 +6,10 @@ from dataclasses import dataclass, field
import numpy as np import numpy as np
import pandas as pd 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): class KukaDataParser(BaseKukaDataParser):
@ -64,7 +67,8 @@ class KukaDataParser(BaseKukaDataParser):
except ValueError as e: except ValueError as e:
raise ValueError(f"Ошибка при изменении формы data_array: {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 @staticmethod
def _extract_channels_data(head: KukaDataHead) -> Tuple[np.ndarray, list]: def _extract_channels_data(head: KukaDataHead) -> Tuple[np.ndarray, list]:
@ -107,7 +111,6 @@ class KukaTextParser(BaseKukaTextParser):
datapack = self._check_msg_flow(line, datapack) datapack = self._check_msg_flow(line, datapack)
return self._datapacks return self._datapacks
def _check_msg_flow(self, line:str, datapack:KukaTXT) -> KukaTXT: def _check_msg_flow(self, line:str, datapack:KukaTXT) -> KukaTXT:
if line == "#BEGINMOTIONINFO": if line == "#BEGINMOTIONINFO":
self._in_msg = True self._in_msg = True
@ -164,94 +167,76 @@ class KukaTextParser(BaseKukaTextParser):
return datapack return datapack
class TraceStageDetector: class TraceStageDetector(BaseTraceStageDetector):
def __init__( def __init__(self, parent:TraceProcessor = None):
self, super().__init__(parent)
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 detect_stages(self, df: pd.DataFrame) -> list: def detect_stages(self, df: pd.DataFrame) -> list:
df = df.sort_index() df = df.sort_index()
stage_markers = [] stage_markers = []
df_indices = df.index.to_list() # времена (в секундах) как список timestamps = df['time'].to_list() # времена (в секундах) как список
for i in df_indices: for i, time in enumerate(timestamps):
mark = self._mark_timestamp(df, i) mark = self._mark_timestamp(df, i, time)
if mark: stage_markers.append(mark) 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 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" # Не интересные значения помечаем как "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" return "unknown"
rob_vel = df.loc[index, "CartVel_Act"] rob_vel = df.loc[index, "CartVel_Act"]
act_vel = df.loc[index, "DriveMotorVel_Act7"] act_vel = df.loc[index, "DriveMotorVel_Act7"]
act_curr = df.loc[index, "DriveMotorCurr_Act7"] act_curr = df.loc[index, "DriveMotorCurr_Act7"]
# 1) Перемещение (moving): если скорость |CartVel_Act| > vel_threshold if abs(rob_vel) > self._parent._settings.filter["robot_zero_velocity_trashold"][0]:
if abs(rob_vel) > self.rob_vel_zero_th: return "Oncomming"
return "moving"
# 2) Создание усилия (force): если |DriveMotorVel_Act7| < act_vel_zero_th и |DriveMotorCurr_Act7| > act_curr_force_th if (abs(act_vel) < self._parent._settings.filter["actuator_zero_velocity_trashold"][0] and
if abs(act_vel) < self.act_vel_zero_th and abs(act_curr) > self.act_curr_force_th: abs(act_curr) > self._parent._settings.filter["actuator_current_trashold"][0]):
return "force" return "Squeeze&Welding&Relief"
# 3) Смыкание (closing): если скорость |DriveMotorVel_Act7| = act_vel_touching if self._parent._settings.filter["actuator_finishing_velocity_trashold"][0] > abs(abs(act_vel) - self._parent._settings.filter["actuator_finishing_velocity"][0]):
if self.act_vel_touching_th > abs(abs(act_vel) - self.act_vel_touching): return "Closing"
return "closing"
return None return None
def _merge_marks_to_stages(self, stage_markers:list, timestamps:list) -> list:
def _merge_marks_to_stages(self, stage_markers:list, df_indices:list) -> list:
stages = [] stages = []
current_stage = stage_markers[0] current_stage = stage_markers[0]
start_time = df_indices[0] start_time = timestamps[0]
for i in range(1, len(stage_markers)): for i in range(1, len(stage_markers)):
if stage_markers[i] != current_stage: if stage_markers[i] != current_stage:
end_time = df_indices[i - 1] end_time = timestamps[i - 1]
stages.append({ stages.append({
"stage": current_stage, "stage": current_stage,
"start_time": start_time, "start_time": start_time,
"end_time": end_time "end_time": end_time
}) })
current_stage = stage_markers[i] current_stage = stage_markers[i]
start_time = df_indices[i] start_time = timestamps[i]
stages.append({ stages.append({
"stage": current_stage, "stage": current_stage,
"start_time": start_time, "start_time": start_time,
"end_time": df_indices[-1] "end_time": timestamps[-1]
}) })
return stages return stages
class TextStageDetector: class TextStageDetector(BaseTextStageDetector):
def __init__(self, def __init__(self, parent :TraceProcessor = None):
region_of_focus: list = [0, 100] super().__init__(parent)
):
self.ROF = region_of_focus
def detect_welding(self, data:list[KukaTXT]): def detect_welding(self, data:list[KukaTXT]) -> list:
stages = [] stages = []
for i in range(len(data)): for i in range(len(data)):
if data[i].func == " SPOT" and data[i].signal == " END": if data[i].func == " SPOT" and data[i].signal == " END":
@ -266,26 +251,34 @@ class TextStageDetector:
class TraceProcessor(BaseRawTraceProcessor): class TraceProcessor(BaseRawTraceProcessor):
def __init__(self): def __init__(self):
self._settings = Settings()
dataparser = KukaDataParser() dataparser = KukaDataParser()
textparser = KukaTextParser() 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: def prerender(self, data:list[str]) -> None:
if data: if data and len(data) == 2:
dat_filepath = data[0]
txt_filepath = None
elif len(data) == 2:
dat_filepath = data[0] dat_filepath = data[0]
txt_filepath = data[1] txt_filepath = data[1]
elif data:
dat_filepath = data[0]
txt_filepath = None
else: else:
dat_filepath = None dat_filepath = None
txt_filepath = None txt_filepath = None
self._trace_df = self._unpack_trace(dat_filepath) trace_df = self._unpack_trace(dat_filepath)
self._text_data = self._unpack_text(txt_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]: def _unpack_trace(self, dat_filepath:str = None) -> Optional[pd.DataFrame]:
if dat_filepath: if dat_filepath:
@ -297,7 +290,44 @@ class TraceProcessor(BaseRawTraceProcessor):
return self._textparser.parse(txt_filepath) return self._textparser.parse(txt_filepath)
return None 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.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.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 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.