Source code for prosi3d.preprocessing.layer

from scipy.signal import savgol_filter
import pandas as pd
import numpy as np
from tqdm import tqdm

[docs]def loopLayerMatchList(path, matchlist, partlist, layer_partids, what='ttldir', correction=0): eostxtfile, paramfile, jobfile, vecdir, ttldir, resdir, logdir, _ = folder2Files(path, 0) polygons = getPolygons(path, partlist) # get ttl files list files = filesInFolder_simple(locals()[what], 'h5') checks = [] for item in files: item = item.rsplit('_')[-1] item = item.rsplit('.', 1)[0] try: num = int(item) except: continue if item == str(num).zfill(5): checks.append(num) checks = np.array(checks) matchlist = np.array(matchlist)+correction matches = checks[np.isin(checks,matchlist)] for lay in matches: print(f'run function for: {lay}') file = path + '\\res\\eosxytime_' + str(lay+correction).zfill(5) + ".h5" # deconstruct string to np array of int partids = layer_partids.loc[lay, 'partIds'] for item in ['[', ',', ']', "'"]: partids = partids.replace(item, '') partids = np.fromstring(partids, dtype=int, sep=' ').astype(int) labelLayer_bindingError(file, partids, polygons) # open xytime for lay # filter partId which has failure end / first true layer # ask all resulting points if in polygon return matches
[docs]def labelLayer_bindingError(file, partids, polygons): ''' open xytime for lay filter partId which has failure end / first true layer ask all resulting points if in polygon ''' xyt = pd.read_hdf(file, 'df') xyt['binding_error1'] = 0 # xyt.loc[xyt['time'] >= 0]['binding_error1'] = 0 xyt['partId'] = xyt['partId'].astype(int) print(f'File opened: {file}') # iterate samples for matching part IDs for row in xyt[{'x_mm', 'y_mm'}].loc[xyt['partId'].isin(partids)].itertuples(): polygon = polygons.loc(row.Index) if point_in_polygon(polygon, (row.x_mm, row.y_mm)): # set label if point in polygon returns true xyt.loc[row.Index, 'binding_error1'] = 1 xyt.to_hdf(file, 'df')
[docs]def errorLayer(folder_stl): ''' Calculate all first layers above missing layers and return sorted list without duplicates ''' # get all files in folder files = filesInFolder_simple(folder_stl, 'txt') # drop duplicates ignoring last seperator like '_' for numbering files = dropFileDuplicates(files, sep='_') layers = [] # stl base name without duplicate number base_name = [] # correlating missing layers base_layers = [] for file in files: # get height by file name of main variant layer = file.rsplit('_', 1)[-1].rsplit('x')[0].split('+') layer = sum([int(item) for item in layer]) + 1 layers.append(layer) # following layers of this part # file name is already reduced to main variant ftxt = folder_stl + '\\' + file + '_0.txt' zduplicate_layers = prosi.getZ(ftxt) layers = layers + zduplicate_layers zduplicate_layers.append(layer) base_name.append(file) base_layers.append(zduplicate_layers) layers = list(dict.fromkeys(layers)) layers.sort() base_and_layers = pd.DataFrame() base_and_layers['base'] = base_name base_and_layers['layers'] = base_layers return layers, base_and_layers
[docs]def reshapeLayerPartIds(error_layers, base_and_layers, partlist): ''' create an array with layers above each error and relevant partIds within that layers ''' ## shape list of error layers to "lay: stl basenames" layer_prt_names = pd.DataFrame(error_layers) layer_prt_names.columns = ['lay'] layer_prt_names.set_index('lay', inplace=True) layer_prt_names['stl_base_names'] = '' for lay in error_layers: for idx, layers in base_and_layers.layers.items(): if np.isin(lay, layers): stl_base = layer_prt_names.loc[lay, 'stl_base_names'] + base_and_layers.loc[idx, 'base'] + ', ' layer_prt_names.loc[lay, 'stl_base_names'] = stl_base ## stl basenames from openjob file partlist = [name.rsplit('_', 1)[0] for name in partlist] partlist = pd.Series(partlist) partlist.index += 1 ## find base name specific partIds as strings and delete line breaks base_and_layers['partIds'] = '' for idx, base in base_and_layers.base.items(): repl = str(partlist.loc[partlist == base].index.values.astype(str)) repl = repl.replace('\n', '') base_and_layers['partIds'].loc[idx] = repl ## replace stl base names with partIds in layer list # THIS IS REALLY IMPORTANT: THE PLUS CHARACTER NEEDS TO BE EXCHANGED WITHIN THE DICTIONARY base_and_layers['base'] = base_and_layers['base'].str.replace('\+', '\\+', regex=True) #base_ids_dict = dict(zip(base_and_layers['base'], base_and_layers['partIds'])) base_ids_dict = pd.Series(base_and_layers['partIds'].values,index=base_and_layers['base']).to_dict() layer_prt_names['partIds'] = layer_prt_names['stl_base_names'].replace(base_ids_dict, regex=True) # alternatives to replace by dict: # for old, new in base_ids_dict.items(): # layer_prt_names['partIds'] = layer_prt_names['stl_base_names'].str.replace(old, new, regex=True) # for row in base_and_layers[{'base', 'partIds'}].itertuples(): # layer_prt_names['partIds'] = layer_prt_names['stl_base_names'].str.replace(row.base, row.partIds, regex=True) ## now we have a dataframe with index=lay == layers above each error and relevant partIds in that layer return layer_prt_names
[docs]def correctLayChange(path, files=[]): ''' Finds layerchanges by unique "laser-off" signal using savitzky-golay-filter and its increase dx If 1500 values are inside boarders then layerchange is found. resulting time delay is 0,03 s beyond laser off TODO: add pausing for 50000 values before next trigger is allowed (peaks in rawsignal sometimes cause double triggering) Args: time, signal Returns: times, lines ''' # if no list is given then process all files in folder if files==[]: # files without check for file name syntax files = filesInFolder_simple(path, typ='h5') if len(files) == 0: print('No files are given!') return for file in tqdm(files): timeline = pd.read_hdf(file, 'df') # # Filter doesnt work if there are zeros or infinity values # timeline.time.dropna(inplace=True) # timeline.ttl.dropna(inplace=True) #global laserdx x_fil = savgol_filter(timeline.ttl, 999, 2) x_fil = pd.Series(x_fil) laserdx = x_fil.diff(periods=1) print(type(laserdx)) lbound = 0.000001 ubound = 0.005 n = 1500 # sampling width ts = timeline.time.diff().median() # convert to int for comparison with index min_dist = int(15 / ts) #print(ts, min_dist) filter_dx = laserdx.loc[(laserdx <= ubound) & (laserdx >= lbound)] changes = [] i = 0 changeidx = -min_dist idx0 = filter_dx.index[0] for idx in filter_dx.index: # only if distance is given if idx > (changeidx + min_dist): if idx == idx0 + 1: i += 1 if i == n: changeidx = idx - n - 1 print(f'layerchange at index {changeidx}') changes.append(changeidx) i = 0 else: i = 0 idx0 = idx # split dataframe cnt = len(changes) n = 0 if cnt > 2: f1, f2 = file.rsplit('.', 1) for idx in changes[0:-1]: fnew = f1 + '_' + str(n) + '.' + f2 n += 1 if n == cnt - 1: # last layer timeline.iloc[idx:,:].to_hdf(fnew, key='df', mode='w') else: timeline.iloc[idx:changes[n],:].to_hdf(fnew, key='df', mode='w')
[docs]def reshape_eos_layer_file(eos_layer_file): ''' Reshapes dataset where two lines represent one weld into a "one line represents one weld" shape Args: eos_layer_file Returns: eos_layer ''' # not really necessary anymore, since all datasets should now be even in length (15.11.2022) if len(eos_layer_file)%2 == 1: starts = eos_layer_file.iloc[1::2].copy() ends = eos_layer_file.iloc[2::2].copy() else: starts = eos_layer_file.iloc[0::2].copy() ends = eos_layer_file.iloc[1::2].copy() ## altenatively drop first row # if len(eos_layer_file)%2 == 1: # eos_layer_file2 = eos_layer_file.drop(eos_layer_file.index[0]) # starts = eos_layer_file2.iloc[0::2].copy() # ends = eos_layer_file2.iloc[1::2].copy() starts.columns = ['x0_mm', 'y0_mm', 'expType', 'prtId'] ends.columns = ['x1_mm', 'y1_mm', 'expType', 'prtId'] starts.reset_index(drop=True, inplace=True) ends.reset_index(drop=True, inplace=True) # does not work if len mismatch in first layer. check first layer slicing! eos_layer = pd.merge(starts, ends, left_index=True, right_index=True) # control for correct expType and prtId if len(eos_layer.loc[(eos_layer['expType_x'] != eos_layer['expType_y']) & (eos_layer['prtId_x'] != eos_layer['prtId_y'])]) == 0: eos_layer.drop(['expType_x', 'prtId_x'], axis='columns', inplace=True) eos_layer.columns = ['x0_mm', 'y0_mm', 'x1_mm', 'y1_mm', 'expType', 'prtId'] print('EOS Layer File successfully reshaped.') else: print('reshape_eos_layer_file: WARNING: Starts and ends could not be merged.') return eos_layer
[docs]def correctContourExpTypes(eos_layer): ''' correct wrong expType of contours if x1 and y1 equal x0 and y0 of following vectors for expTypes 1, 2, 3 Nicht korrigierte Upskin downskins könnten der Fehler in den bisherigen versionen gewesen sein! (15.11.2022) Args: eos_layer Returns: eos_layer ''' eos_layer['x0_next'] = eos_layer['x0_mm'].shift(periods=-1, axis=0, fill_value=-1) eos_layer['y0_next'] = eos_layer['y0_mm'].shift(periods=-1, axis=0, fill_value=-1) index_downskin_contours = eos_layer.loc[(eos_layer['x1_mm'] == eos_layer['x0_next']) & (eos_layer['y1_mm'] == eos_layer['y0_next']) & (eos_layer.expType == 1)].index eos_layer.loc[(index_downskin_contours | index_downskin_contours + 1), 'expType'] = 4 index_infill_contours = eos_layer.loc[(eos_layer['x1_mm'] == eos_layer['x0_next']) & (eos_layer['y1_mm'] == eos_layer['y0_next']) & (eos_layer.expType == 2)].index eos_layer.loc[(index_infill_contours | index_infill_contours + 1), 'expType'] = 5 index_upskin_contours = eos_layer.loc[(eos_layer['x1_mm'] == eos_layer['x0_next']) & (eos_layer['y1_mm'] == eos_layer['y0_next']) & (eos_layer.expType == 3)].index eos_layer.loc[(index_upskin_contours | index_upskin_contours + 1), 'expType'] = 6 print(f'Changed exposures: {len(index_downskin_contours)} (down), {len(index_infill_contours)} (infill), {len(index_upskin_contours)} (up)') return eos_layer
[docs]def assign_weld_speed(path, eos_layer): ''' input path and eos_layer with corrected exposure types --> correctContourExpTypes() add welding speeds to each line based on parameter file Args: path, eos_layer Returns: eos_layer ''' _, paramfile, _, _, _, _, logdir, _ = prosi.folder2Files(path, 0) param = pd.read_csv(paramfile, sep=';') param.columns = ['prtId', 'partname', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'] # true partid starts counting by 1 param['prtId'] = param['prtId'] + 1 parts = eos_layer['prtId'].unique() for part in parts: # select all exposures for this partid exp_types = eos_layer.loc[(eos_layer['prtId'] == part), 'expType'].unique() for exptype in exp_types: speed = param.loc[(param['prtId'] == part), str(exptype)] eos_layer.loc[(eos_layer['expType'] == exptype) & (eos_layer['prtId'] == part), 'speed'] = speed.iloc[0] return eos_layer
[docs]def ttl2Welds(layer, path, tresh1=1, tresh2=-1): ''' New version of tJumpTTL() returning dataframe for ttlweld class. ttlweld class equals weld class where one entry represents one weld. duration returned is maybe one sampling width too long - (substraction added 28.11.2022) 16.11.2022 Args: layer (integer), path, tresh1=1, tresh2=-1 Returns: lsr_strt_jmp_len_jmp_strt_lsr_len (pandas dataframe) ''' eostxtfile, paramfile, jobfile, vecdir, ttldir, resdir, logdir, _ = prosi.folder2Files(path, 0) f = ttldir + "\\ch4raw_" + str(layer).zfill(5) + ".h5" df = pd.read_hdf(f, 'df') df.columns = ['time', 'lse', 'ttl', 'kse_bp', 'kse_rec'] df = df.drop(['lse', 'kse_bp', 'kse_rec'], axis=1) # difference of indexes marks laserweld by values greater than one and by that the next jump min_len_idx = 1 # first value above threshold is laser on for the first time power = df['ttl'].loc[(df['ttl'] >= tresh1)].index[0] # loc gets index, iloc counts places # temporal difference between each sample df['diffs'] = df['time'].diff() # use median of timediffs for sampling time step in ms. 50 kHz = 0.02 ms sampling_ms = df['diffs'].median()*1000 # threshold boarder, empiric value for 50kHz was around row 7000 -> 0.3 s for any sampling frequ # corrected to calculate it directly #indexboarder = power + int(0.294/sampling_ms*1000) power_1s = power + int(1/sampling_ms*1000) df_temp = df[power:power_1s].copy() indexboarder = df_temp.loc[(df_temp['ttl'] > (tresh1+1.5))].index[-1] del df_temp # jumps = all values lower then thresholds represent jumps and vice versa for welds jumps = df.loc[((df['ttl'] < tresh1) & (df.index < indexboarder)) | ((df['ttl'] < tresh2) & (df.index >= indexboarder))].copy() welds = df.loc[((df['ttl'] >= tresh1) & (df.index < indexboarder)) | ((df['ttl'] >= tresh2) & (df.index >= indexboarder))].copy() ## JUMPS # new column with index integers jumps['idx'] = jumps.index # values greater 1 in diff_idx are idx-lengths of laserwelds between jumps jumps['diff_idx'] = jumps.idx.diff() # if difference between indices greater min_len_idx then laser was off for that time # rows with start of jump end length of weld before jmp_strt_lsr_len = jumps.loc[jumps['diff_idx'] > min_len_idx].copy() ## WELDS vice versa welds['idx'] = welds.index welds['diff_idx'] = welds.idx.diff() lsr_strt_jmp_len = welds.loc[welds['diff_idx'] > min_len_idx].copy() # calculate temporal length diff_ms using first and last entry jmp_strt_lsr_len['prev_idx'] = jmp_strt_lsr_len.index - jmp_strt_lsr_len['diff_idx'] jmp_strt_lsr_len['diff_ms'] = (jmp_strt_lsr_len['time'] - df.loc[jmp_strt_lsr_len['prev_idx'], 'time'].values)*1000 lsr_strt_jmp_len['prev_idx'] = lsr_strt_jmp_len.index - lsr_strt_jmp_len['diff_idx'] lsr_strt_jmp_len['diff_ms'] = (lsr_strt_jmp_len['time'] - df.loc[lsr_strt_jmp_len['prev_idx'], 'time'].values)*1000 # timelaser in lsr_strt_jmp_len gives timeval for laservector start (high ramp) --> use for synchronization with eos data jmp_strt_lsr_len['exp_time'] = jmp_strt_lsr_len['time'] - df.loc[power, 'time'] lsr_strt_jmp_len['exp_time'] = lsr_strt_jmp_len['time'] - df.loc[power, 'time'] jmp_strt_lsr_len.drop(['ttl', 'diffs', 'idx', 'prev_idx', 'diffs'], axis='columns', inplace=True) lsr_strt_jmp_len.drop(['ttl', 'diffs', 'idx', 'prev_idx', 'diffs'], axis='columns', inplace=True) ## shift to have laser weld start, length as well as following jump start and length in the exact same row jmp_strt_lsr_len.reset_index(drop=False, inplace=True) lsr_strt_jmp_len.reset_index(drop=False, inplace=True) ## without following two lines merged dataset would look like: #lsr_strt_jmp_len_jmp_strt_lsr_len.columns = ['idx0_weld', 't0_weld', 'diff_idx_prev_jump', 'diff_ms_prev_jump', 'time2power_weld', 'idx0_prev_jump', 't0_prev_jump', 'diff_idx_prev_weld', 'diff_ms_prev_weld', 'time2power_prev_jump'] lsr_strt_jmp_len = lsr_strt_jmp_len.append(lsr_strt_jmp_len.iloc[-1].copy(), ignore_index=True) lsr_strt_jmp_len = lsr_strt_jmp_len.shift(periods=1, axis=0) lsr_strt_jmp_len_jmp_strt_lsr_len = pd.merge(lsr_strt_jmp_len, jmp_strt_lsr_len, left_index=True, right_index=True) lsr_strt_jmp_len_jmp_strt_lsr_len.columns = ['idx0_weld', 't0_weld', 'diff_idx_prev_jump', 'diff_ms_prev_jump', 'time2power_weld', 'idx0_next_jump', 't0_next_jump', 'diff_idx_weld', 'diff_ms_weld', 'time2power_next_jump'] lsr_strt_jmp_len_jmp_strt_lsr_len['diff_idx_prev_jump'] = lsr_strt_jmp_len_jmp_strt_lsr_len['diff_idx_prev_jump'].shift(periods=-1, axis=0) lsr_strt_jmp_len_jmp_strt_lsr_len['diff_ms_prev_jump'] = lsr_strt_jmp_len_jmp_strt_lsr_len['diff_ms_prev_jump'].shift(periods=-1, axis=0) lsr_strt_jmp_len_jmp_strt_lsr_len.columns = ['idx0_weld', 't0_weld', 'diff_idx_next_jump', 'diff_ms_next_jump', 'time2power_weld', 'idx0_next_jump', 't0_next_jump', 'diff_idx_weld', 'diff_ms_weld', 'time2power_next_jump'] # complete first row entries lsr_strt_jmp_len_jmp_strt_lsr_len.loc[0, 'idx0_weld'] = lsr_strt_jmp_len_jmp_strt_lsr_len.loc[0, 'idx0_next_jump'] - lsr_strt_jmp_len_jmp_strt_lsr_len.loc[0, 'diff_idx_weld'] lsr_strt_jmp_len_jmp_strt_lsr_len.loc[0, 't0_weld'] = lsr_strt_jmp_len_jmp_strt_lsr_len.loc[0, 't0_next_jump'] - (lsr_strt_jmp_len_jmp_strt_lsr_len.loc[0, 'diff_ms_weld']/1000) lsr_strt_jmp_len_jmp_strt_lsr_len.loc[0, 'time2power_weld'] = lsr_strt_jmp_len_jmp_strt_lsr_len.loc[0, 't0_weld'] - df.loc[power, 'time'] # substract sampling width for accurate result lsr_strt_jmp_len_jmp_strt_lsr_len['diff_ms_next_jump'] = lsr_strt_jmp_len_jmp_strt_lsr_len['diff_ms_next_jump'] - sampling_ms lsr_strt_jmp_len_jmp_strt_lsr_len['diff_ms_weld'] = lsr_strt_jmp_len_jmp_strt_lsr_len['diff_ms_weld'] - sampling_ms return lsr_strt_jmp_len_jmp_strt_lsr_len