dev: создан новый метод PassportFormer для обработки клиентских трейсов
This commit is contained in:
parent
21fd64d7f7
commit
7b759a86d2
@ -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:
|
||||
...
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -330,3 +330,4 @@ class ClientAnalyzerWidget(QWidget):
|
||||
main_layout.addWidget(self._tabWidget)
|
||||
main_layout.addWidget(button_widget)
|
||||
self.setLayout(main_layout)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
"Перемещение"
|
||||
|
||||
@ -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.
|
Loading…
Reference in New Issue
Block a user