diff --git a/client.py b/client.py new file mode 100644 index 0000000..c6a23d9 --- /dev/null +++ b/client.py @@ -0,0 +1,93 @@ +import socket +import struct +import json +import os +import PySimpleGUI as sg + + +def recvdata(conn, filepath): + header_size = struct.unpack('i', conn.recv(4))[0] + header_bytes = conn.recv(header_size) + header_json = header_bytes.decode('utf-8') + header_dic = json.loads(header_json) + content_len = header_dic['contentlen'] + content_name = header_dic['contentname'] + recv_len = 0 + pdf = os.path.join(filepath, content_name) + with open(pdf, 'wb') as file: + while recv_len < content_len: + correntrecv = conn.recv(1024 * 1000) + file.write(correntrecv) + recv_len += len(correntrecv) + + +def senddata(conn, path, select_type): + name = os.path.basename(os.path.realpath(path)) + try: + with open(path, 'rb') as file: + content = file.read() + headerdic = dict( + contentlen=len(content), + contentname=name, + select_type=select_type + ) + headerjson = json.dumps(headerdic) + headerbytes = headerjson.encode('utf-8') + headersize = len(headerbytes) + conn.send(struct.pack('i', headersize)) + conn.send(headerbytes) + conn.sendall(content) + except ConnectionResetError: + print('不存在这个文件!') + + +def connect(): + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + dest_ip = '192.168.11.121' + dest_port = int(8190) + client.connect((dest_ip, dest_port)) + return client + + +def transclient(sendfile, resfile, select_type): + conn = connect() + senddata(conn, sendfile, select_type) + recvdata(conn, resfile) + + +def make_gui(): + while True: + layout = [ + [sg.Text('选择排样程序')], + [sg.Radio('T7', 'layout', key='t7', default=True), sg.Radio('nova', 'layout', key='nova'), + sg.Radio('Xplus', 'layout', key='xplus')], + [ + sg.Text('导入排样excel')], + [ + sg.Input(key='_FILE1_'), sg.FileBrowse()], + [ + sg.Text('生成排样位置')], + [ + sg.Input(key='_FILE2_'), sg.FolderBrowse()], + [ + sg.OK(), sg.Cancel()]] + window = sg.Window('解码排样测试程序', layout) + event, values = window.Read() + if event == 'OK': + if values['nova']: + select_type = 'nova' + elif values['xplus']: + select_type = 'xplus' + else: + select_type = 't7' + transclient(values['_FILE1_'], os.path.join(values['_FILE2_'], select_type)) + sg.Popup('排样成功!') + else: + window.Close() + break + + +if __name__ == '__main__': + # respath = 'result' + # transclient(os.path.join('example', '0520_T7_1.xlsx'), respath) + make_gui() diff --git a/result/assignments_06300937_06300937_t1.xlsx b/result/assignments_06300937_06300937_t1.xlsx new file mode 100644 index 0000000..fc1b042 Binary files /dev/null and b/result/assignments_06300937_06300937_t1.xlsx differ diff --git a/result/assignments_07041654_07041654_0704_nova_1.xlsx b/result/assignments_07041654_07041654_0704_nova_1.xlsx new file mode 100644 index 0000000..5699184 Binary files /dev/null and b/result/assignments_07041654_07041654_0704_nova_1.xlsx differ diff --git a/result/assignments_07051629_07051629_0608(1)(1).xlsx b/result/assignments_07051629_07051629_0608(1)(1).xlsx new file mode 100644 index 0000000..2552478 Binary files /dev/null and b/result/assignments_07051629_07051629_0608(1)(1).xlsx differ diff --git a/result/assignments_07051649_07051649_0704_nova_1.xlsx b/result/assignments_07051649_07051649_0704_nova_1.xlsx new file mode 100644 index 0000000..fb39957 Binary files /dev/null and b/result/assignments_07051649_07051649_0704_nova_1.xlsx differ diff --git a/rule/lib_type_limit.xlsx b/rule/lib_type_limit.xlsx new file mode 100644 index 0000000..0697c68 Binary files /dev/null and b/rule/lib_type_limit.xlsx differ diff --git a/tools/client.py b/server.py similarity index 57% rename from tools/client.py rename to server.py index b32b1e1..df4ea8a 100644 --- a/tools/client.py +++ b/server.py @@ -4,6 +4,11 @@ import json import os from datetime import datetime +from tools.common import basedir + +from tools.t7 import AutoLayout as T7 +from tools.novaplus import AutoLayout as NovaPlus + def recvdata(conn, path): """ @@ -18,15 +23,16 @@ def recvdata(conn, path): header_dic = json.loads(header_json) content_len = header_dic['contentlen'] content_name = header_dic['contentname'] + select_type = header_dic['select_type'] recv_len = 0 - fielpath = os.path.join(path, '%s_%s' %(datetime.now().strftime("%m%d%H%M"), content_name)) + fielpath = os.path.join(path, '%s_%s' % (datetime.now().strftime("%m%d%H%M"), content_name)) file = open(fielpath, 'wb') while recv_len < content_len: correntrecv = conn.recv(1024 * 1000) file.write(correntrecv) recv_len += len(correntrecv) file.close() - return fielpath + return fielpath, select_type def senddata(conn, path, message=None): @@ -57,4 +63,27 @@ def senddata(conn, path, message=None): conn.sendall(path.encode('utf-8')) +def server(): + myserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + adrss = ("", 8191) + myserver.bind(adrss) + myserver.listen(5) + while True: + try: + myclient, adddr = myserver.accept() + recv_content, select_type = recvdata(myclient, os.path.join(basedir, 'example')) + if select_type == 'nova': + layout = NovaPlus(recv_content, data_limit=800) + elif select_type == 'xplus': + layout = NovaPlus(recv_content, data_limit=400) + else: + layout = T7(recv_content, data_limit=1520) + outputpath = layout.run() + senddata(myclient, outputpath) + except Exception as e: + print(e) + continue + +if __name__ == '__main__': + server() diff --git a/tools/common.py b/tools/common.py new file mode 100644 index 0000000..6f9149a --- /dev/null +++ b/tools/common.py @@ -0,0 +1,20 @@ +import os +import logging + +basedir = os.path.dirname(os.path.realpath(__file__)) + + +def log(name): + """ + 日志 + :param name: + :return: 返回logger对象 + """ + logger = logging.getLogger('main') + logpath = os.path.join(basedir, 'log', name + '.log.txt') + logfile = logging.FileHandler(logpath, mode='w') + logfomat = logging.Formatter('%(message)s\t%(asctime)s') + logfile.setFormatter(logfomat) + logfile.setLevel(logging.DEBUG) + logger.addHandler(logfile) + return logger diff --git a/tools/novaplus.py b/tools/novaplus.py new file mode 100644 index 0000000..f9c2d6c --- /dev/null +++ b/tools/novaplus.py @@ -0,0 +1,243 @@ +import os +import socket + +import pandas as pd +from collections import defaultdict +from datetime import datetime +import time +import logging +import os +from .common import basedir, log + + +class AutoLayout: + """ + 自动化派样 + """ + + def __init__(self, path, output=basedir, data_limit=1600): + self.path = path + self.output = output + self.data_limit = data_limit + + self.index_assignments = defaultdict(list) + # 芯片数量量大小 + self.chip_size = dict() + # 芯片是否极致 + self.chip_type = dict() + # 芯片barcode + self.chip_barcode_recode = defaultdict(set) + # 芯片原始数据读取 + self.ori_data = self.read_excel() + # 当前锚芯片 + self.loc_chip_num = 1 + # 芯片 文库计数 + self.chip_lib_type = defaultdict(dict) + + self.logger = log(os.path.basename(f'{path}.txt')) + self.return_log = list() + + def read_excel(self): + """ + 原始数据处理 + :return: + """ + merge = pd.read_excel(self.path, None) + ori_data = dict() + for name, sheet in merge.items(): + sheet.fillna('.', inplace=True) + ori_data[name] = sheet.to_dict('records') + return ori_data + + def add_new_data(self, chipname, library_data, newer=True): + """ + 增加新数据到已知芯片上 + :param chipname: + :param library_data: + :param newer: + :return: + """ + self.index_assignments[chipname].extend(library_data['data']) + self.chip_barcode_recode[chipname].update({item['barcode'] for item in library_data['data']}) + if newer: + self.chip_size[chipname] = library_data['size'] + + else: + self.chip_size[chipname] += library_data['size'] + if library_data['lib_type'] in self.chip_lib_type[chipname]: + self.chip_lib_type[chipname][library_data['lib_type']] += library_data['size'] + else: + self.chip_lib_type[chipname][library_data['lib_type']] = library_data['size'] + + def dec_barcode_radio(self, chipname): + data = self.index_assignments[chipname] + df = pd.DataFrame(data) + barcode_df = pd.DataFrame(df['barcode'].str.split('', expand=True).iloc[:, 1:-1].values, + columns=['T' + str(x) for x in range(16)]).join(df['data_needed']) + total = barcode_df['data_needed'].sum() + is_not_balance_list = [] + for i in range(16): + column = 'T' + str(i) + col_df = barcode_df.groupby(column).agg({'data_needed': 'sum'}) + # 去掉N计数 + if 'N' in col_df.index: + base_N_size = col_df.loc['N', 'data_needed'] + col_df = col_df.drop('N') + else: + base_N_size = 0 + col_df['ratio'] = (col_df['data_needed']) / (total - base_N_size) + + need_base_list = list() + ratio = col_df['ratio'].to_dict() + + for decbase in ['A', 'T', 'C']: + if decbase not in ratio: + ratio[decbase] = 0 + need_base_list.append(decbase) + continue + if ratio[decbase] < 0.1: + need_base_list.append(decbase) + # 小于标准的base 是不是空的,空的说明都满足 + if len(need_base_list) > 2: + is_not_balance_list.append( + '[%s] 第%s位置, %s 有碱基不平衡,算出结果为 %s' % (chipname, i, need_base_list, ratio) + ) + # 对于G不能超过10% + if 'G' not in ratio: + ratio['G'] = 0 + if ratio['G'] > 0.7: + is_not_balance_list.append( + '[%s] 第%s位置, G 含量超过70%%,算出结果为 %s' % (chipname, i, ratio['G']) + ) + if is_not_balance_list: + self.return_log.extend(is_not_balance_list) + print('有碱基不平衡性!\n', '\n'.join(is_not_balance_list)) + + @staticmethod + def read_rule(): + df = pd.read_excel(os.path.join(basedir, 'rule', 'lib_type_limit.xlsx')) + return df.to_dict('index') + + @staticmethod + def level(row): + if row['customer'] == '百奥益康' and '3\'' in row['lib_type']: + return 1 + elif row['customer'] == '百奥益康' and '5\'' in row['lib_type']: + return 2 + else: + return 100 + + def judge_data(self, chipname, library_data): + size = library_data['size'] + library = library_data['library'] + + # 芯片大小不能超过设定限制 + sizelimit = True + if self.chip_size[chipname] + size > self.data_limit: + sizelimit = False + self.logger.error(f'{library} {chipname} 文库相加大于设定限制') + + # barcode有重复 + notrepeatbarcode = True + if self.chip_barcode_recode[chipname].intersection({item['barcode'] for item in library_data['data']}): + notrepeatbarcode = False + self.logger.error(f'{library} {chipname} 文库有barcode重复') + + # 特定文库不能超过限制 + sp_lib1 = True + for _, myrule in self.read_rule().items(): + lib_type = myrule['lib_type'] + limit = myrule['limit'] + if lib_type in self.chip_lib_type[chipname]: + if self.chip_lib_type[chipname][lib_type] + size > self.data_limit * limit: + sp_lib1 = False + self.logger.error(f'{library} {chipname} 文库有大于设定限制') + break + + if sizelimit and notrepeatbarcode and sp_lib1: + return True + return False + + def assign_samples(self): + ori_library_data = list() + ori_library_df = pd.DataFrame(self.ori_data['未测']) + ori_library_df['level'] = ori_library_df.apply(self.level, axis=1) + for library, library_df in ori_library_df.groupby('#library'): + ori_library_data.append(dict( + library=library, + size=library_df['data_needed'].sum(), + time=library_df['time'].values[0], + customer=library_df['customer'].values[0], + level=library_df['level'].values[0], + status=library_df['status'].values[0], + lib_type=library_df['lib_type'].values[0], + data=library_df.to_dict('records') + )) + ori_sort_data = sorted(ori_library_data, key=lambda x: (x['level'], x['customer'], -x['size'], x['time'])) + + while ori_sort_data: + library_data = ori_sort_data[0] + chipname = f'lane{self.loc_chip_num}' + + # 空白芯片直接添加 + if chipname not in self.index_assignments: + self.add_new_data(chipname, library_data) + ori_sort_data.remove(library_data) + continue + + # 判断条件 + if self.judge_data(chipname, library_data): + self.add_new_data(chipname, library_data, newer=False) + ori_sort_data.remove(library_data) + else: + for j in range(len(ori_sort_data)): + newlibrary_data = ori_sort_data[j] + if self.judge_data(chipname, newlibrary_data): + ori_sort_data.remove(newlibrary_data) + self.add_new_data(chipname, newlibrary_data, newer=False) + break + j += 1 + else: + # 代表接下来的数据放到这个chip当中都不行,只有换chip了 + self.loc_chip_num += 1 + # 加完之后下面的数据可能加上去就慢了就换chip + if self.chip_size[chipname] > self.data_limit * 0.99: + self.loc_chip_num += 1 + + def run(self): + try: + self.assign_samples() + except Exception as e: + self.return_log.append(f'排样出错, 请联系!{e}') + self.index_assignments = {} + outputname = 'assignments_%s_%s' % (datetime.now().strftime("%m%d%H%M"), os.path.basename(self.path)) + outputpath = os.path.join(self.output, 'result', outputname) + writer = pd.ExcelWriter(outputpath) + + no_assign_data = list() + no_assign_chip = list() + for chip_idx, chip_assignments in self.index_assignments.items(): + self.dec_barcode_radio(chip_idx) + df = pd.DataFrame(chip_assignments) + if df['data_needed'].sum() < self.data_limit * 0.8: + no_assign_chip.append(chip_idx) + no_assign_data.extend(chip_assignments) + continue + df.to_excel(writer, sheet_name=chip_idx, index=False) + pd.DataFrame(no_assign_data).to_excel(writer, sheet_name='未测', index=False) + if self.return_log: + log_res = [splog for splog in self.return_log if + not any(f'[{chip}]' in str(splog) for chip in no_assign_chip)] + pd.DataFrame(log_res).to_excel(writer, sheet_name='log', index=False) + writer.close() + return outputpath + +if __name__ == '__main__': + start_time = time.time() + excel_file = 'example/0704_nova_1.xlsx' + output_file = '' + layout = AutoLayout(excel_file, output_file, data_limit=800) + layout.run() + end_time = time.time() + execution_time = end_time - start_time + print(f"代码执行时间为:{execution_time} 秒") diff --git a/main.py b/tools/t7.py similarity index 86% rename from main.py rename to tools/t7.py index 9abbdbc..b7b7ea7 100644 --- a/main.py +++ b/tools/t7.py @@ -1,32 +1,10 @@ -import os -import socket - import pandas as pd from collections import defaultdict from datetime import datetime import time -import logging import os -from tools.client import recvdata, senddata - -basedir = os.path.dirname(os.path.realpath(__file__)) - - -def log(name): - """ - 日志 - :param name: - :return: 返回logger对象 - """ - logger = logging.getLogger('main') - logpath = os.path.join(basedir, 'log', name + '.log.txt') - logfile = logging.FileHandler(logpath, mode='w') - logfomat = logging.Formatter('%(message)s\t%(asctime)s') - logfile.setFormatter(logfomat) - logfile.setLevel(logging.DEBUG) - logger.addHandler(logfile) - return logger +from .common import basedir, log class AutoLayout: @@ -160,7 +138,7 @@ class AutoLayout: '%s 第%s位置, %s 有碱基不平衡,算出结果为 %s' % (chipname, i, need_base_list, ratio) ) - if len(is_not_balance_list) >2 : + if len(is_not_balance_list) > 2: self.return_log.append('有碱基不平衡性!') self.return_log.extend(is_not_balance_list) print('有碱基不平衡性!\n', '\n'.join(is_not_balance_list)) @@ -285,11 +263,14 @@ class AutoLayout: pass def run(self): - self.assign_samples() + try: + self.assign_samples() + except Exception as e: + self.return_log.append(f'排样出错, 请联系!{e}') + self.index_assignments = {} outputname = 'assignments_%s_%s' % (datetime.now().strftime("%m%d%H%M"), os.path.basename(self.path)) outputpath = os.path.join(self.output, 'result', outputname) writer = pd.ExcelWriter(outputpath) - no_assign_data = list() for chip_idx, chip_assignments in self.index_assignments.items(): self.dec_barcode_radio(chip_idx) @@ -309,32 +290,14 @@ class AutoLayout: return outputpath -def server(): - myserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - adrss = ("", 8190) - myserver.bind(adrss) - myserver.listen(5) - while True: - try: - myclient, adddr = myserver.accept() - recv_content = recvdata(myclient, os.path.join(basedir, 'example')) - print('接收到了文件') - layout = AutoLayout(recv_content) - outputpath = layout.run() - senddata(myclient, outputpath) - except Exception as e: - print(e) - continue - - if __name__ == '__main__': - # start_time = time.time() - # excel_file = 'example/06211429_包lane广西.xlsx' - # output_file = '' - # layout = AutoLayout(excel_file, output_file) - # layout.run() - # end_time = time.time() - # execution_time = end_time - start_time - # print(f"代码执行时间为:{execution_time} 秒") + start_time = time.time() + excel_file = 'example/07031754_20230703.xlsx' + output_file = '' + layout = AutoLayout(excel_file, output_file) + layout.run() + end_time = time.time() + execution_time = end_time - start_time + print(f"代码执行时间为:{execution_time} 秒") - server() + # server()