活动介绍

start_index = -1 for i, line in enumerate(lines): if 'Ini' in line: start_index = i + 1 if start_index == -1: print('Error: "Initialization " was not found in the log files.') exit()

时间: 2024-03-10 18:50:29 浏览: 111
这段代码的作用是在一个日志文件中查找包含字符串 `'Ini'` 的行,并将其后一行的行号赋值给变量 `start_index`。如果找不到这个字符串,则输出错误信息并退出程序。但是这段代码也存在一些问题。其中一个问题是:`if` 语句缺少冒号。应该在 `if start_index == -1` 后面加上冒号。另外,如果找不到字符串 `'Ini'`,应该输出错误信息并返回 `None` 而不是退出程序。下面是修改后的代码: ``` start_index = -1 for i, line in enumerate(lines): if 'Ini' in line: start_index = i + 1 break if start_index == -1: print('Error: "Ini" was not found in the log files.') return None ```
相关问题

import configparser import re, os import openpyxl,csv import glob from openpyxl.styles import PatternFill STR_SPACE1 = " " DATA_BYTE1 = 8 wb = None ws = None byteRow = "" memoRow = "" config = {} def init_config(): print("init_config call") global config con = configparser.ConfigParser() con.read("FunctionTest.ini") for sec in con.sections(): tmp = {} key: object for (key, val) in con.items(sec): tmp[key] = val config[sec] = tmp def _reduce(xls_arr): new_xls = [] for line in xls_arr: if len(new_xls) == 0: new_xls.append(line) else: find = 0 for item in new_xls: if item["DataPos"] == line["DataPos"] and item["DataLength"] == line["DataLength"] and item["CANID"] == line["CANID"]: find = 1 break if find == 0: new_xls.append(line) return new_xls # 1.1 open xlsx def s1_generate_xls_array(xls, sheet, MsgLabel, CANID, DataPos, DataLength, debug): xls_data = [] last_msg_label = "" last_can_id = "" global wb, ws wb = openpyxl.load_workbook(xls) ws = wb[sheet] steps = {} stepstart = 9 stepend = 0 if sheet == "1Trip動作確認(HV)" or sheet == "1Trip動作確認" or sheet == "1Trip動作確認(ISG)" or sheet == "CAN出力データ_HV_CSA" or sheet == "CAN出力データ_ISG_CSA": stepend = 38 elif sheet == "CAN出力データ_HV_ICS" or sheet == "CAN出力データ_ISG_ICS": stepend = 23 elif sheet == "MUTE_クリソナ検知表示": stepend = 56 elif sheet == "ソナー音量のカスタマイズ対応" or sheet == "MUTE_RCDブザー吹鳴あり": stepend = 23 for row_num, row in enumerate(ws.iter_rows(), start=1): print("%s %s %s" % (xls, sheet, row_num)) data_pos = "" data_len = "" msg_label = "" can_id = "" step1expectval = "" for (col_num, cell) in enumerate(row, start=1): idx = str(col_num) print ( str(row_num) +" "+ str(col_num) +" " + str( cell.value)) if (MsgLabel == idx): msg_label = str(cell.value) elif (CANID == idx): can_id = str(cell.value) elif (DataPos == idx): data_pos = str(cell.value) elif (DataLength == idx): data_len = str(cell.value) elif (int(idx) >= int(stepstart) and int(idx) <= int(stepend) ): if ("12" == idx): step1expectval = str(cell.value) ddd=str(cell.value) if -1 != str(cell.value).find("step"): steps[str(cell.value)] = ws.cell(row_num + 2, col_num ).value #row_num + 1 ########################### #temp = ws.cell(row_num + 1, col_num ).fill.start_color.rgb #print( "66 14 = " + str(ws.cell(66, 14 ).fill.start_color.rgb)) ########################### print("---") if msg_label and msg_label.upper() != "NONE": last_msg_label = msg_label if can_id and can_id.upper() != "NONE": last_can_id = can_id if can_id == 119: print() if data_pos and data_len: line = _check_xls_data(last_msg_label, last_can_id, msg_label, can_id, data_pos, data_len, step1expectval) if line: line["row"] = row_num xls_data.append(line) xls_data = _reduce(xls_data) if int(debug) > 0: # print("write %s %s" % (xls, sheet)) f = openpyxl.Workbook() table = f.create_sheet(sheet, 0) table.cell(row=1, column=1, value="msg_label") table.cell(row=1, column=2, value="can_id") table.cell(row=1, column=3, value="data_pos") table.cell(row=1, column=4, value="data_length") for (line_num, lines) in enumerate(xls_data, start=2): # print("write line %s" % str(line_num)) table.cell(row=line_num, column=1, value=lines["MsgLabel"]) table.cell(row=line_num, column=2, value=lines["CANID"]) table.cell(row=line_num, column=3, value=lines["DataPos"]) table.cell(row=line_num, column=4, value=lines["DataLength"]) # f.save(os.path.splitext(xls)[0] + "_lines.xlsx") f.save(sheet + "_lines.xlsx") return xls_data,steps def _check_xls_data(last_msg_label, last_can_id, msg_label, can_id, data_pos, data_len, step1expectval): lines = {} if not data_pos: return "" if not data_len: return "" if "-" == step1expectval or "ー" == step1expectval or "‐" == step1expectval or "-" == step1expectval or "―" == step1expectval: return "" if re.match("[1-3]?[0-9]\.?[0-8]?$", data_pos) and re.match("[\d][\d]?$", data_len): lines["DataPos"] = data_pos lines["DataLength"] = data_len if msg_label.upper() == "NONE": msg_label = last_msg_label if can_id.upper() == "NONE": can_id = last_can_id lines["MsgLabel"] = msg_label.upper().strip() lines["CANID"] = can_id.upper().strip() return lines else: return "" # 2.1 open log file <LOOP> # 2.2 search need line from log # 2.3 generate log array def s2_generate_log_array(): file_array = {} for sec, items in config.items(): if not re.match("log", sec, re.IGNORECASE): continue debug = items.get("debug", "") # sheet = items.get("sheetname", "") for fname in glob.glob("**/*.asc", recursive=True): farr = [] print("1/3 analyse %s" % fname) with open(fname, 'r', encoding="utf-8", errors="ignore") as f: while True: line = f.readline() if not line: break else: match1 = re.match( "\s*([\d]+\.[\d]{6})\sCANFD\s+[\d]+\s+[a-zA-Z]{2}\s+([a-zA-Z0-9]+)\s+[a-zA-Z0-9]+\s+[\d]\s[\d]\s[a-zA-Z0-9]\s+[\d]{1,2}((\s[a-zA-Z0-9]{2})+)\s", line) match2 = re.match( "\s*([\d]+\.[\d]{6})\s+[\d]+\s+([\w]+)\s+[\w]+\s+d\s[\d]\s([0-9A-F\s]+)\s", line) if match1: farr.append([item.upper().strip() for item in list(match1.groups())]) if match2: farr.append([item.upper().strip() for item in list(match2.groups())]) file_array[fname] = farr if int(debug) > 0: f = openpyxl.Workbook() count = 0 for (fname, arr) in file_array.items(): print("1/3 write " + fname) table = f.create_sheet("log_sheet" + str(count)) table.cell(row=1, column=1, value=fname) for (line_num, line) in enumerate(arr, start=2): # print("write " + str(line_num)) for (col_num, cell) in enumerate(line, start=1): table.cell(row=line_num, column=col_num, value=cell) count = count + 1 f.save("_log.xlsx") return file_array def jionLogMsgDataBin(msgdata, bytestart, bitpos, bitlength): msgdatabin = bin(int("1" + str(msgdata).replace(STR_SPACE1, ""), 16))[3:] bit_end = bytestart * DATA_BYTE1 - bitpos bit_start = bit_end - bitlength log_data_value = int(msgdatabin[bit_start:bit_end], 2) return log_data_value def _append_array(array, item): arraysize = len(array) if 0 == arraysize: array.append(item) else: #jionLogMsgDataBin(array[arraysize - 1]["data"]) if not ( array[arraysize - 1]["CANID"] == item["CANID"] and array[arraysize - 1]["dataval"] == item["dataval"] and array[arraysize - 1]["DataPos"] == item["DataPos"] and array[arraysize - 1]["DataLength"] == item["DataLength"]): array.append(item) #print(array) # 3.3 get whole data 0 0 8 8 08 00 00 00 00 c0 00 47 def s3_get_whole_data(xls_data,steps, file_array, debug): # 3.1 get line from line array allstepsdata = {} _len = len(xls_data) for (cn, xls_lines) in enumerate(xls_data): print("%d/%d anaylse" % (cn, _len)) msg_label = xls_lines["MsgLabel"] can_id = xls_lines["CANID"] # 3.2 match log from log array stepdata = {} for (fname, arr) in file_array.items(): whole_data = {} for (line_num, line) in enumerate(arr): # if len(line) < 5: # continue if line[1].zfill(3) == can_id: # print("data " + line[4]) temp = xls_lines.get("data", "") if not xls_lines.get("data", ""): xls_lines["data"] = "" if not xls_lines.get("data", ""): xls_lines["timestamp"] = line[0] xls_lines["data"] = line[2] # new_xls.append(xls_lines) xls_lines = s3_get_bytes(xls_lines) dataval = jionLogMsgDataBin(xls_lines["data"], int(xls_lines["DataPos"].split(".")[0]), int(xls_lines["DataPos"].split(".")[1]), int(xls_lines["DataLength"])) xls_lines["dataval"] = dataval if whole_data.get(msg_label, ""): whole_data[msg_label].append(xls_lines) else: whole_data[msg_label] = [] #open(msg_label + ".csv", "w") whole_data[msg_label].append(xls_lines) else: #if xls_lines.get("dataval", "") != dataval: new_lines = {} new_lines["MsgLabel"] = msg_label new_lines["CANID"] = can_id new_lines["data"] = line[2] new_lines["timestamp"] = line[0] new_lines["DataPos"] = xls_lines["DataPos"] new_lines["DataLength"] = xls_lines["DataLength"] # new_xls.append(new_lines) new_lines = s3_get_bytes(new_lines) dataval = jionLogMsgDataBin( new_lines["data"], int(new_lines["DataPos"].split(".")[0]), int(new_lines["DataPos"].split(".")[1]), int(new_lines["DataLength"])) new_lines["dataval"] = dataval if whole_data.get(msg_label, ""): _append_array(whole_data[msg_label], new_lines) # whole_data[msg_label].append(new_lines) else: whole_data[msg_label] = [] # whole_data[msg_label].append(new_lines) _append_array(whole_data[msg_label], new_lines) print("") stepdata[fname] = whole_data allstepsdata[xls_lines["MsgLabel"]+"("+xls_lines["DataPos"]+")"] = stepdata return allstepsdata ''' def _write_log(whole_data): for (label, arr) in whole_data.items(): with open(label + ".csv", "a+") as f: for new_lines in arr: f.write("%s,%s,%s,%s,%s,%s,%s\n" % (new_lines["timestamp"], #new_lines["MsgLabel"], new_lines["CANID"], new_lines["CANID"], new_lines["DataPos"], new_lines["DataLength"], new_lines.get("data", ""), new_lines.get("l8", ""), str(new_lines.get("bytes", "")))) ''' def _expand_str(data): new = "0x" + data.replace(" ", "") size = int(len(new)/2 - 1)#2->3 s16 = int(new, 16) s64 = str(bin(s16))[2:].zfill(size*8) l8 = [] for i in range(size): l8.append(s64[0 + i * 8:8 + i * 8]) return l8 def _analyse_pos(pos): match = re.match("([\d]+)\.([\d]+)", pos) if (match): return (int(match.group(1)), int(match.group(2))) else: return (int(pos), -1) def _get_bytes(l8, begin, end, leng): front = [] if 1 < begin: [front.append(l8[idx]) for idx in range(begin - 1)] else: front = [] if end == -1: front.append(l8[begin - 1][:8]) else: front.append(l8[begin - 1][:8 - end]) whole = "".join(front) return whole[len(whole) - leng:] # 3.4 get bytes 08 00 00 00 00 c0 00 47 # 3.5 put to line array def s3_get_bytes(xls_lines): msg_label = xls_lines["MsgLabel"] can_id = xls_lines["CANID"] pos = xls_lines["DataPos"] data_length = xls_lines["DataLength"] data = xls_lines.get("data", "") if not data: xls_lines["bytes"] = "" else: l8 = _expand_str(data) xls_lines["l8"] = "-".join(l8) (begin, end) = _analyse_pos(pos) strTmp = _get_bytes(l8, begin, end, int(data_length)) xls_lines["bytes"] = str(int(strTmp,2))# print("bytes " + xls_lines["bytes"]) return xls_lines # 4.1 check result # 4.2 output result def s4_check_output(sheet, steps, xls_data, data,xls): lenval = len(xls_data) steps1=steps.keys() #stepm=[] for i in steps1: #stepm.append(i) for ii in memoRow: del memoRow[0] for (cn, xls_lines) in enumerate(xls_data): print("%d/%d output" % (cn, lenval)) msg_label = xls_lines["MsgLabel"]+"("+xls_lines["DataPos"]+")" msg_label1 = xls_lines["MsgLabel"] #读取msg can_id = xls_lines["CANID"] pos = xls_lines["DataPos"] data_length = xls_lines["DataLength"] msgdict = data.get(msg_label, []) msgdict1 = msgdict.get(i,[]) # msgdict2 = msgdict1.get(msg_label1,[])# mbytes = [] if len(msgdict): for new_lines in msgdict2: if ( # new_lines["MsgLabel"] == msg_label and new_lines["CANID"] == can_id and new_lines["DataPos"] == pos and new_lines["DataLength"] == data_length): #if not new_lines.get("bytes", "") in mbytes: mbytes.append(new_lines.get("bytes", "")) if len(mbytes): #ws[str(byteRow) + str(xls_lines.get("row"))] = mbytes[0] ws[str(ii) + str(xls_lines.get("row"))] = "→".join(mbytes[0:]) break wb.save(sheet + "_result.xlsx") wb.save(xls) def getbasicsteplines(actstr, startlineinde,linesdata): stepdata = [] nextstartindex = 0 nextstartindex1=0 index = startlineinde global SELECT1 for sec, items in config.items(): if not re.match("OUT", sec, re.IGNORECASE): continue SELECT1 = items.get("select", "") shiftno = int(actstr.split(",")[0]) actflg = actstr.split(",")[1] #actno = int(actstr.split(",")[2]) while index < len(linesdata): tempstr = linesdata[index] #PTCURSFTcan,PTCURSFTDPX,PTCURSFTDPY,PTCURSFTDL if tempstr[1].strip().zfill(3).upper() == PTCURSFTcan: tempshift = jionLogMsgDataBin(tempstr[2], PTCURSFTDPX, PTCURSFTDPY, PTCURSFTDL) if(SELECT1=="PTCU" or SELECT1=="BPRND"): if tempshift == 0: print(1) else: if shiftno != tempshift: break if(SELECT1=="SFTP" or SELECT1=="XPRND" or SELECT1=="JICSXPRND" or SELECT1=="JCSAXPRND"): if shiftno != tempshift: break if actflg == "125" : if tempstr[1].strip().zfill(3).upper() == "1A0": tempshift = jionLogMsgDataBin(tempstr[2], 3, 2, 11) if tempshift == 125: index = nextstartindex break else: nextstartindex = index stepdata.append(tempstr) index = index + 1 return stepdata ,index def getmixsteplines(actstr, startlineinde,linesdata): stepdata = [] index = startlineinde nextstartindex = 0 csr_mutestr = "" global SELECT1 for sec, items in config.items(): if not re.match("OUT", sec, re.IGNORECASE): continue SELECT1 = items.get("select", "") shiftno = int(actstr.split(",")[0]) actflg = actstr.split(",")[1] actno = int(actstr.split(",")[2]) #startFlag=False #endFlag= False while index < len(linesdata): tempstr = linesdata[index] if tempstr[1].strip().zfill(3).upper() == PTCURSFTcan: tempshift = jionLogMsgDataBin(tempstr[2], PTCURSFTDPX, PTCURSFTDPY, PTCURSFTDL) if(SELECT1=="XPRND" or SELECT1=="BPRND"): if tempshift == 0: print(1) else: if shiftno != tempshift: break if(SELECT1=="SFTP" or SELECT1=="XPRND" or SELECT1=="JICSXPRND" or SELECT1=="JCSAXPRND"): if shiftno != tempshift: break if actflg == "1" and actno == 0: if tempstr[1].strip().zfill(3).upper() == "51D": tempshift = jionLogMsgDataBin(tempstr[2], 1, 7, 1) if tempshift == 1: index = nextstartindex break else: nextstartindex = index if actflg == "1" and actno != 0: if tempstr[1].strip().zfill(3).upper() == "51D": tempshift = jionLogMsgDataBin(tempstr[2],1, 7, 1) if tempshift == 1: print("1") if len(csr_mutestr) == 0: csr_mutestr = csr_mutestr + str(tempshift) else: if int (csr_mutestr[len(csr_mutestr) - 1]) != tempshift: csr_mutestr = csr_mutestr + str(tempshift) else: print("0") nextstartindex = index if len(csr_mutestr) == 0: csr_mutestr = csr_mutestr + str(tempshift) else: if int (csr_mutestr[len(csr_mutestr) - 1]) != tempshift: csr_mutestr = csr_mutestr + str(tempshift) if "0101" == csr_mutestr: index = nextstartindex break stepdata.append(tempstr) index = index + 1 return stepdata ,index def getmixsteplines1(actstr, startlineinde,linesdata): stepdata = [] index = startlineinde nextstartindex = 0 csr_mutestr = "" global SELECT1 for sec, items in config.items(): if not re.match("OUT", sec, re.IGNORECASE): continue SELECT1 = items.get("select", "") shiftno = int(actstr.split(",")[0]) actflg = actstr.split(",")[1] actno = int(actstr.split(",")[2]) #startFlag=False #endFlag= False while index < len(linesdata): tempstr = linesdata[index] if tempstr[1].strip().zfill(3).upper() == PTCURSFTcan: tempshift = jionLogMsgDataBin(tempstr[2], PTCURSFTDPX, PTCURSFTDPY, PTCURSFTDL) if(SELECT1=="XPRND" or SELECT1=="BPRND"): if tempshift == 0: print(1) else: if shiftno != tempshift: break if(SELECT1=="SFTP" or SELECT1=="XPRND" or SELECT1=="JICSXPRND" or SELECT1=="JCSAXPRND"): if shiftno != tempshift: break if actflg == "0" and actno == 1: if tempstr[1].strip().zfill(3).upper() == "51D": tempshift = jionLogMsgDataBin(tempstr[2], 2, 3, 12) if tempshift == 193: index = nextstartindex break else: nextstartindex = index if actflg == "193" and actno == 1: if tempstr[1].strip().zfill(3).upper() == "390": tempshift = jionLogMsgDataBin(tempstr[2],1, 4, 4) if tempshift == 0: index = nextstartindex break else: nextstartindex = index stepdata.append(tempstr) index = index + 1 return stepdata ,index def file_array_steps(file_array, steps): allsteps_filearray = {} stepssize = len(steps) #farrallsteps = {} startlineinde = 0 #stepdata = [] for stepindex in steps: stepdata = [] if 10 == stepssize: print("1Trip動作確認(HV) or 1Trip動作確認(ISG)") print("1Trip動作確認") #linecnt = len(file_array.values) for linekey in file_array: linesdata = file_array[linekey] if stepindex == "step1" or stepindex == "step2" : if stepindex == "step1" : stepdata, cur_lineindex =getbasicsteplines(ONETRIP_BASIC_ACT[stepindex], startlineinde, linesdata) allsteps_filearray[stepindex] = stepdata startlineinde = cur_lineindex else: stepdata= allsteps_filearray["step1"] allsteps_filearray[stepindex] = stepdata startlineinde =startlineinde else: stepdata, cur_lineindex =getbasicsteplines(ONETRIP_BASIC_ACT[stepindex], startlineinde, linesdata) allsteps_filearray[stepindex] = stepdata startlineinde =cur_lineindex elif 16 == stepssize: print("MUTE_クリソナ検知表示") for linekey in file_array: linesdata = file_array[linekey] stepdata, cur_lineindex =getmixsteplines(ONETRIP_MUTE_CRISINA[stepindex], startlineinde, linesdata) allsteps_filearray[stepindex] = stepdata startlineinde =cur_lineindex elif 5 == stepssize: print("ソナー音量のカスタマイズ対応") for linekey in file_array: linesdata = file_array[linekey] if stepindex == "step1" or stepindex == "step2" : if stepindex == "step1" : stepdata, cur_lineindex =getmixsteplines1(ONETRIP_BASIC_ACT[stepindex], startlineinde, linesdata) allsteps_filearray[stepindex] = stepdata startlineinde = cur_lineindex else: stepdata= allsteps_filearray["step1"] allsteps_filearray[stepindex] = stepdata startlineinde =startlineinde else: stepdata, cur_lineindex =getmixsteplines1(ONETRIP_BASIC_ACT[stepindex], startlineinde, linesdata) allsteps_filearray[stepindex] = stepdata startlineinde =cur_lineindex allsteps_filearray[stepindex] = stepdata return allsteps_filearray def main(): xls = "" sheet = "" global byteRow, memoRow,PTCURSFTcan,PTCURSFTDPX,DISselect,PTCURSFTDPY,PTCURSFTDL,SELECT,SELECT1,ONETRIP_BASIC_ACT,ONETRIP_MUTE_CRISINA,ONETRIP_MUTE_CRISINA1,ONETRIP_MUTE_RCD print("1/3 analyse log") file_array = s2_generate_log_array() for sec, items in config.items(): if not re.match("OUT", sec, re.IGNORECASE): continue xls = items.get("xls", "") sheet = items.get("sheetname", "") msgLabel = items.get("msglabel", "") canid = items.get("canid", "") dataPos = items.get("datapos", "") dataLength = items.get("datalength", "") debug = items.get("debug", "") byteRow = items.get("bytes", "") memoRow1 = items.get("memo", "") memoRow = memoRow1.split() PTCURSFTcan = items.get("ptcursftc", "") PTCURSFTDPX1 = items.get("ptcursftdpxx", "") PTCURSFTDPX = int(PTCURSFTDPX1) PTCURSFTDPY1 = items.get("ptcursftdpyy", "") PTCURSFTDPY = int(PTCURSFTDPY1) #Disselect = items.get("disselect", "") #DISselect = int(Disselect) PTCURSFTDL1 = items.get("ptcursftdll", "") PTCURSFTDL = int(PTCURSFTDL1) SELECT1 = items.get("select", "") if SELECT1=="XPRND" : ONETRIP_BASIC_ACT = {"step1":"0,0,0","step2":"0,0,0","step3":"8,125,0","step4":"8,0,0","step5":"4,0,0", "step6":"2,0,0","step7":"4,0,0","step8":"8,0,0","step9":"0,0,0","step10":"0,0,0"} ONETRIP_MUTE_CRISINA = {"step1":"0,0,0", "step2":"8,1,0", "step3":"8,1,1", "step4":"8,1,2", "step5":"8,1,3", "step6":"8,1,4", "step7":"4,1,0", "step8":"4,1,1", "step9":"2,1,0", "step10":"2,1,1", "step11":"4,1,0", "step12":"4,1,1", "step13":"8,1,0", "step14":"8,1,1", "step15":"0,0,0", "step16":"0,0,0"} ONETRIP_MUTE_CRISINA1 = {"step1":"2,0,1", "step2":"2,0,1", "step3":"2,193,1", "step4":"2,193,0", "step5":"2,0,0"} xls_data = [] steps = {} if not sheet: continue # 1.2 search need line from xlsx # 1.3 generate line array if xls and sheet and msgLabel and canid and dataPos and dataLength: print("2/3 analyse %s sheet %s" % (xls, sheet)) xls_data, steps = s1_generate_xls_array(xls, sheet, msgLabel, canid, dataPos, dataLength, debug) print("3/3 get data from log") allsteps_filearray = file_array_steps(file_array, steps) #data = s3_get_whole_data(xls_data, steps, file_array, debug) data = s3_get_whole_data(xls_data, steps, allsteps_filearray, debug) #_write_log(data) # s4_arr = s3_get_bytes(s3_arr) s4_check_output(sheet, steps , xls_data, data,xls) else: print("config file error") if __name__ == '__main__': init_config() main()

<think>我们面对的是一个复杂的CAN数据分析脚本,主要用于处理Excel测试用例和日志文件,提取CAN数据并生成报告。整个脚本分为几个主要部分:配置初始化、Excel解析、日志解析、数据匹配和结果输出。下面我将逐一解释关键部分。##1.全局变量和配置初始化- `wb`,`ws`:用于操作Excel工作簿和工作表。- `byteRow`, `memoRow`:配置中定义的输出列。 -`config`:存储配置文件内容。- `init_config()`:读取配置文件"FunctionTest.ini"。##2.Excel处理函数###`s1_generate_xls_array(xls,sheet,...)` -**功能**:解析Excel文件,提取测试用例信息(MsgLabel,CANID, DataPos,DataLength)和步骤信息(step1~stepN)。 -**关键步骤**:1.打开Excel文件,选择指定工作表。2.遍历每一行,提取所需列的信息。3.通过`_check_xls_data`验证数据有效性。4.使用`_reduce`去重(保留第一个出现的测试项)。5.如果开启调试,将解析结果写入新Excel文件。 ###`_check_xls_data(last_msg_label, ...)`-验证数据位置(DataPos)和长度(DataLength)的格式,并构建测试项字典。##3.日志处理函数### `s2_generate_log_array()`- **功能**:读取所有.asc日志文件,解析每一行,提取时间戳、CAN ID和数据字段。 -**关键步骤**:1.使用`glob`遍历所有.asc文件。2.使用正则表达式匹配两种格式的日志行。3.将解析结果存入字典`file_array`,键为文件名,值为日志行列表。##4.数据匹配与处理### `s3_get_whole_data(xls_data,steps,file_array, debug)`- **功能**:将Excel测试项与日志数据进行匹配,提取指定位置的数据值。 -**关键步骤**:1.遍历每个测试项(xls_data)。2.遍历每个日志文件(file_array)。3.在日志中匹配相同CANID的帧。4.使用`jionLogMsgDataBin`提取数据值(根据DataPos和DataLength)。5.使用`_append_array`避免重复添加连续相同的数据。6.结果存储在`allstepsdata`中,结构为:`{"MsgLabel(DataPos)": {文件名:{ "MsgLabel": [匹配的数据行 ]} }}`### `jionLogMsgDataBin(msgdata, bytestart, bitpos,bitlength)`-将CAN数据(十六进制字符串)转换为二进制字符串,然后根据指定的字节起始、位偏移和长度提取整数值。##5.结果输出###`s4_check_output(sheet,steps,xls_data,data,xls)`- **功能**:将匹配到的数据值写入Excel的对应步骤列中。 -**关键步骤**:1.遍历每个步骤(steps)。2.遍历`memoRow`(配置中定义的输出列,如A、B等)。3.遍历每个测试项,从`data`中获取匹配的数据值(多个值用"→"连接)。4.写入Excel并保存。##6.步骤划分函数### `file_array_steps(file_array, steps)`- **功能**:根据配置的步骤定义,将日志数据按步骤划分。 -根据不同的测试类型(如1Trip動作確認、MUTE_クリソナ検知表示等)使用不同的函数(`getbasicsteplines`,`getmixsteplines`)划分步骤。 -划分依据:根据换挡信息(PTCURSFTcan)或其他特定CAN信号的值变化。 ##7.主函数`main()`-流程:1.初始化配置。2.解析日志文件(s2_generate_log_array)。3.遍历配置中的OUT部分,处理每个Excel工作表。4.解析Excel测试用例(s1_generate_xls_array)。5.划分日志步骤(file_array_steps)。6.匹配数据(s3_get_whole_data)。7.输出结果(s4_check_output)。##8.辅助函数- `_expand_str(data)`:将十六进制字符串转换为二进制字符串列表(按字节分割)。 -`_analyse_pos(pos)`:解析数据位置字符串(如"1.0")为(字节,位)。- `_get_bytes(l8,begin,end,leng)`:从二进制字符串列表中提取指定位。##优化建议1. **全局变量**:尽量减少全局变量的使用,通过参数传递。2.**函数拆分**:部分函数过长(如`s1_generate_xls_array`),可进一步拆分。3.**错误处理**:增加异常处理,确保文件操作安全。 4. **性能**:对于大日志文件,考虑更高效的数据结构(如使用字典索引CAN ID)。 ##示例:数据提取过程假设测试项: -MsgLabel:"MotorSpeed"- CANID:"51D"- DataPos:"1.0"- DataLength:16在日志中匹配到CAN ID为51D的帧数据:"0000000000 C00047",则:1.转换为二进制字符串(按字节):['00000000','00000000', ...] 2.根据DataPos "1.0":起始字节1,位0,提取16位(即第一个字节的全部8位和第二个字节的前8位)。 3.得到二进制字符串"0000000000000000",转换为整数0。 ##§§相关问题§§: 1.在函数`jionLogMsgDataBin`中,为什么使用`bin(int("1"+msgdata,16))[3:]`?这样做有什么目的? 2.在步骤划分函数`getbasicsteplines`中,如何根据换挡信号(PTCURSFTcan)划分步骤?请举例说明。 3.函数`_append_array`是如何避免添加重复数据的?它判断重复的依据是什么? 4.在`s4_check_output`函数中,为什么使用`memoRow`列表来管理输出列?如何确保每个步骤写入正确的列?</think># CAN数据分析工具全面解析 这个脚本是一个完整的CAN数据分析工具,用于处理Excel测试用例和.asc日志文件,提取CAN数据并生成分析报告。下面我将详细解释整个系统的结构和关键功能。 ## 整体架构 ```mermaid graph TD A[主程序] --> B[初始化配置] A --> C[解析日志文件] A --> D[处理Excel测试用例] D --> E[数据匹配] E --> F[结果输出] C --> G[步骤划分] G --> E ``` ## 关键功能模块详解 ### 1. 配置初始化 (`init_config()`) ```python def init_config(): global config con = configparser.ConfigParser() con.read("FunctionTest.ini") for sec in con.sections(): tmp = {} for (key, val) in con.items(sec): tmp[key] = val config[sec] = tmp ``` - **功能**:从INI配置文件读取设置 - **关键参数**: - `xls`: Excel文件路径 - `sheetname`: 工作表名称 - `msglabel`, `canid`: CAN消息标识 - `datapos`, `datalength`: 数据位置和长度 - `bytes`, `memo`: 结果输出位置 ### 2. Excel测试用例处理 (`s1_generate_xls_array()`) ```python def s1_generate_xls_array(xls, sheet, MsgLabel, CANID, DataPos, DataLength, debug): global wb, ws wb = openpyxl.load_workbook(xls) ws = wb[sheet] steps = {} xls_data = [] # 遍历Excel行 for row_num, row in enumerate(ws.iter_rows(), start=1): for (col_num, cell) in enumerate(row, start=1): # 提取关键字段 if MsgLabel == str(col_num): msg_label = str(cell.value) # ...其他字段提取... # 验证并存储有效数据 if data_pos and data_len: line = _check_xls_data(...) if line: line["row"] = row_num xls_data.append(line) # 去重处理 xls_data = _reduce(xls_data) return xls_data, steps ``` - **功能**:解析Excel中的测试用例 - **关键步骤**: 1. 打开Excel工作簿 2. 遍历每一行提取测试参数 3. 验证数据有效性 (`_check_xls_data`) 4. 去重处理 (`_reduce`) 5. 返回测试用例列表和步骤定义 ### 3. 日志文件处理 (`s2_generate_log_array()`) ```python def s2_generate_log_array(): file_array = {} for sec, items in config.items(): if not re.match("log", sec, re.IGNORECASE): continue # 遍历所有.asc文件 for fname in glob.glob("**/*.asc", recursive=True): farr = [] with open(fname, 'r', encoding="utf-8", errors="ignore") as f: while True: line = f.readline() # 正则匹配两种日志格式 match1 = re.match(...) match2 = re.match(...) if match1 or match2: farr.append([item.upper().strip() for item in list(match.groups())]) file_array[fname] = farr return file_array ``` - **功能**:解析所有.asc日志文件 - **关键点**: - 使用正则表达式匹配两种CAN日志格式 - 递归搜索所有子目录中的.asc文件 - 返回结构:`{文件名: [[时间戳, CAN ID, 数据], ...]}` ### 4. 数据提取与处理 (`s3_get_whole_data()`) ```python def s3_get_whole_data(xls_data, steps, file_array, debug): allstepsdata = {} for (cn, xls_lines) in enumerate(xls_data): stepdata = {} for (fname, arr) in file_array.items(): whole_data = {} for (line_num, line) in enumerate(arr): if line[1].zfill(3) == xls_lines["CANID"]: # 提取数据值 dataval = jionLogMsgDataBin( line[2], int(xls_lines["DataPos"].split(".")[0]), int(xls_lines["DataPos"].split(".")[1]), int(xls_lines["DataLength"]) ) # 存储结果 if whole_data.get(xls_lines["MsgLabel"], ""): _append_array(whole_data[xls_lines["MsgLabel"]], new_lines) else: whole_data[xls_lines["MsgLabel"]] = [new_lines] stepdata[fname] = whole_data key = f"{xls_lines['MsgLabel']}({xls_lines['DataPos']})" allstepsdata[key] = stepdata return allstepsdata ``` - **功能**:匹配测试用例与日志数据 - **核心技术**: - `jionLogMsgDataBin()`:根据字节/位偏移提取数据值 - `_append_array()`:避免重复添加相同数据 - 多层嵌套结构存储结果 ### 5. 数据值提取算法 (`jionLogMsgDataBin()`) ```python def jionLogMsgDataBin(msgdata, bytestart, bitpos, bitlength): # 移除空格并转换为二进制字符串 hex_str = str(msgdata).replace(STR_SPACE1, "") bin_str = bin(int("1" + hex_str, 16))[3:] # 添加前导1避免前导零丢失 # 计算位范围 bit_end = bytestart * DATA_BYTE1 - bitpos bit_start = bit_end - bitlength # 提取并转换值 return int(bin_str[bit_start:bit_end], 2) ``` - **输入**:原始数据、字节起始位置、位偏移、位长度 - **输出**:提取的整数值 - **示例**: - 输入:`msgdata="08 00"`, `bytestart=1`, `bitpos=0`, `bitlength=8` - 处理:`08 00` → `0000100000000000` - 提取:字节1的8位 → `00001000` (8) ### 6. 结果输出 (`s4_check_output()`) ```python def s4_check_output(sheet, steps, xls_data, data, xls): steps1 = steps.keys() for i in steps1: # 遍历所有步骤 for ii in memoRow: # 遍历输出列 del memoRow[0] # 移除已使用的列 for (cn, xls_lines) in enumerate(xls_data): # 遍历测试项 # 获取匹配的数据值 mbytes = [] msg_label = f"{xls_lines['MsgLabel']}({xls_lines['DataPos']})" msg_data = data.get(msg_label, {}).get(i, {}).get(xls_lines['MsgLabel'], []) for new_lines in msg_data: if (new_lines["CANID"] == xls_lines["CANID"] and new_lines["DataPos"] == xls_lines["DataPos"] and new_lines["DataLength"] == xls_lines["DataLength"]): mbytes.append(new_lines.get("bytes", "")) # 写入Excel if mbytes: cell = f"{ii}{xls_lines.get('row')}" ws[cell] = "→".join(mbytes) # 保存结果 wb.save(sheet + "_result.xlsx") wb.save(xls) ``` - **功能**:将分析结果写入Excel - **特点**: - 按步骤和测试项组织输出 - 使用箭头连接多个值(如"01→02→03") - 保存原始文件和结果文件 ### 7. 步骤划分逻辑 (`file_array_steps()`) ```python def file_array_steps(file_array, steps): allsteps_filearray = {} startlineindex = 0 for stepindex in steps: stepdata = [] # 根据测试类型选择划分策略 if "1Trip" in sheet: # 基本步骤划分 stepdata, startlineindex = getbasicsteplines( ONETRIP_BASIC_ACT[stepindex], startlineindex, linesdata ) elif "MUTE" in sheet: # 复杂步骤划分 stepdata, startlineindex = getmixsteplines( ONETRIP_MUTE_CRISINA[stepindex], startlineindex, linesdata ) allsteps_filearray[stepindex] = stepdata return allsteps_filearray ``` - **功能**:将日志数据按测试步骤划分 - **划分依据**: - 换挡信号值 (`PTCURSFTcan`) - 特定CAN信号的变化(如ID 51D的第7位) - 配置文件中定义的步骤序列 ## 系统运行流程 1. **初始化**:读取配置文件(`FunctionTest.ini`) 2. **日志处理**:解析所有.asc日志文件 3. **测试用例处理**:解析Excel中的测试项 4. **步骤划分**:根据配置将日志数据分段 5. **数据匹配**:提取每个测试项对应的数据值 6. **结果输出**:将提取的值写入Excel报告 ## 典型应用场景 1. **汽车电子测试**:验证CAN总线信号是否符合预期 2. **故障诊断**:分析特定条件下的信号变化 3. **自动化测试**:批量处理多个测试用例 4. **数据报告生成**:创建可追溯的测试报告 ##

def parse_model_config(path): """Parses the yolo-v3 layer configuration file and returns module definitions""" file = open(path, 'r') lines = file.read().split('\n') lines = [x for x in lines if x and not x.startswith('#')] lines = [x.rstrip().lstrip() for x in lines] # get rid of fringe whitespaces module_defs = [] for line in lines: if line.startswith('['): # This marks the start of a new block module_defs.append({}) module_defs[-1]['type'] = line[1:-1].rstrip() if module_defs[-1]['type'] == 'convolutional': module_defs[-1]['batch_normalize'] = 0 else: key, value = line.split("=") value = value.strip() module_defs[-1][key.rstrip()] = value.strip() return module_defs

<think>好的,我现在需要仔细分析用户提供的`parse_model_config`函数,并按照之前的格式结构进行解析。用户之前询问了数据配置相关的代码,现在转向模型配置解析,所以我需要确保回答结构清晰,符合他们的要求。 首先,理解这个函数的功能。根据注释,这个函数用于解析YOLO-v3的层配置文件,返回模块定义。用户给出的代码中,函数读取配置文件,处理每一行,提取模块类型和参数,最终生成一个字典列表。我需要拆解每个步骤的作用,并解释其应用场景。 接下来,按照之前的模板结构,我需要分几个部分:核心功能、关键组件解析、应用场景、对比传统写法、常见问题及解决方案、最佳实践和扩展技巧。需要确保每个部分都详细且易于理解。 首先,核心功能部分,要简要说明函数的目的。这个函数主要是解析模型配置文件,生成结构化的模块定义列表,供模型构建使用。比如,YOLO的配置文件通常包含卷积层、上采样层、路由层等,每个模块有不同的参数。 关键组件解析部分,需要逐行分析代码。例如,打开文件、处理行、去除注释和空白、遍历行来构建模块定义。需要注意如何处理以'['开头的行作为新模块,以及如何分割键值对。特别要提到,对于卷积层,默认设置batch_normalize为0,这可能影响模型结构。 应用场景部分,需要说明这个函数在模型构建中的用途,比如在加载YOLO模型时,根据配置文件动态创建层。还可以举出具体的例子,如Darknet-53的主干网络构建,或YOLO检测头的配置。 与传统写法的对比,可能需要对比硬编码模型结构与使用配置文件的灵活性。比如,修改网络结构只需改配置文件而不需要改代码,提升可维护性。 常见问题部分,需要考虑可能的错误,比如文件不存在、键错误、参数类型错误等,给出相应的解决方案。例如,文件路径错误导致无法打开,或者配置文件格式不正确导致解析失败。 最佳实践建议可能包括使用YAML等更结构化的格式,增加异常处理,参数校验等。例如,在读取文件时添加try-except块,确保文件存在,或者校验必要参数是否存在。 扩展应用技巧方面,可以提到如何支持多模型配置,动态调整超参数,或者添加自定义层类型。例如,根据不同的任务选择不同的配置文件,或者在解析时添加额外的参数处理逻辑。 需要注意的是,用户可能对YOLO的配置文件结构不熟悉,所以需要简要说明配置文件的常见结构,比如每个模块用方括号定义类型,下面是键值对参数。例如: [convolutional] batch_normalize=1 filters=32 size=3 这样的例子有助于用户理解解析后的数据结构。 另外,要确保解释清楚module_defs的结构,每个元素是一个字典,包含'type'和对应的参数。比如,第一个模块可能是卷积层,包含batch_normalize、filters等参数。 在编写过程中,要使用中文,避免Markdown格式,但按照用户之前的示例,他们可能希望保留一定的结构,比如分节和代码块。需要确认用户是否接受这样的格式,但根据历史记录,之前的回答使用了分节和代码块,所以可以继续沿用。 最后,需要检查是否有遗漏的关键点,比如函数中的细节处理,如去除行首行尾空格,处理注释行等。这些细节对于确保解析正确很重要,需在解析中说明。 总结整个思考过程,我需要逐步拆解函数,解释每部分代码的作用,结合实际应用场景,对比优缺点,提供问题解决方法和优化建议,确保用户能够全面理解该函数的功能和实现方式。</think>--- ### 一、**代码核心功能** 该函数的 **核心目标** 是解析YOLO模型的配置文件(如`yolov3.cfg`),将其转换为结构化的模块定义列表。每个模块定义描述神经网络层的类型(如卷积层、上采样层)及其参数(如过滤器数量、步长等),供后续模型构建使用。 ```python def parse_model_config(path): """解析YOLO模型配置文件,返回模块定义列表""" # 步骤拆解: # 1. 读取配置文件 → 2. 清洗数据 → 3. 逐行解析 → 4. 构建结构化字典 file = open(path, 'r') lines = file.read().split('\n') # 按行分割文本 lines = [x for x in lines if x and not x.startswith('#')] # 过滤空行和注释 lines = [x.rstrip().lstrip() for x in lines] # 去除首尾空格 module_defs = [] # 存储模块定义的列表 for line in lines: if line.startswith('['): # 检测到新模块的开始 module_defs.append({}) # 添加空字典表示新模块 module_defs[-1]['type'] = line[1:-1].rstrip() # 提取模块类型(如'convolutional') # 特殊处理:为卷积层设置默认参数 if module_defs[-1]['type'] == 'convolutional': module_defs[-1]['batch_normalize'] = 0 # 默认禁用批量归一化 else: # 处理键值对参数 key, value = line.split("=") # 分割参数名和值 module_defs[-1][key.rstrip()] = value.strip() # 存储到当前模块字典 return module_defs ``` --- ### 二、**关键组件解析** #### 1. **文件读取与清洗** - **`file.read().split('\n')`** 将配置文件按行分割为列表,例如: ```ini [convolutional] filters=32 size=3 ``` 转换为:`['[convolutional]', 'filters=32', 'size=3']` - **过滤注释与空行** `lines = [x for x in lines if x and not x.startswith('#')]` 移除以`#`开头的注释行和空行。 #### 2. **模块类型识别** - **方括号标记模块头** 当检测到以`[`开头的行(如`[convolutional]`),表示新模块开始,创建空字典并记录模块类型。 #### 3. **参数解析逻辑** - **键值对分割** 使用`split("=")`处理非模块头的行,例如将`filters=32`分割为`key="filters"`, `value="32"`。 - **特殊默认值处理** 对`convolutional`类型模块,默认设置`batch_normalize=0`(需注意实际配置可能覆盖此值)。 --- ### 三、**输入输出示例** #### 输入配置文件片段 (`yolov3.cfg`) ```ini [convolutional] batch_normalize=1 filters=64 size=3 stride=2 [upsample] stride=2 ``` #### 输出结构化数据 ```python [ { 'type': 'convolutional', 'batch_normalize': '1', # 注意:此处值被覆盖为字符串 'filters': '64', 'size': '3', 'stride': '2' }, { 'type': 'upsample', 'stride': '2' } ] ``` --- ### 四、**典型应用场景** #### 场景1:动态构建YOLO模型 ```python # 根据解析结果创建PyTorch模块 from models.yolo import Darknet model = Darknet("yolov3.cfg") # 内部调用parse_model_config解析配置 model.load_state_dict(torch.load("weights.pth")) ``` #### 场景2:模型结构可视化 ```python # 打印模块定义列表 config = parse_model_config("yolov3.cfg") for i, layer in enumerate(config): print(f"Layer {i}: {layer['type']} | Params: {layer}") ``` #### 场景3:参数覆盖调试 ```python # 手动修改特定层的参数 config = parse_model_config("yolov3.cfg") config[3]['filters'] = '128' # 修改第4层的过滤器数量 ``` --- ### 五、**与传统写法的对比** #### ❌ 硬编码模型结构(不推荐) ```python # 直接在代码中定义网络结构(灵活性差) class YOLO(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=3) # 结构变更需修改代码 ``` #### ✅ 配置文件驱动(推荐) ```python # 通过修改配置文件调整网络结构 # 代码无需改动即可适配不同模型变体 ``` --- ### 六、**常见问题与解决方案** | 问题现象 | 原因分析 | 解决方案 | |-----------------------------|-------------------------|----------------------------| | `KeyError` | 参数未正确分割 | 检查行中是否包含合法的`=`符号 | | 数值参数被误存为字符串 | 未进行类型转换 | 添加`int(value)`/`float(value)`转换 | | `IndexError` | 配置文件格式错误(如未以`[`开头) | 验证配置文件是否符合标准格式 | | 缺少必要参数 | 配置文件中遗漏关键字段 | 添加默认值或严格校验配置 | --- ### 七、**最佳实践建议** #### 1. 增强类型转换 ```python # 自动转换数值型参数 key = key.rstrip() value = value.strip() if value.isdigit(): module_defs[-1][key] = int(value) elif value.replace('.', '', 1).isdigit(): # 简单浮点数检测 module_defs[-1][key] = float(value) else: module_defs[-1][key] = value ``` #### 2. 添加必要校验 ```python # 检查必要参数是否存在 required_params = { 'convolutional': ['filters', 'size', 'stride'], 'route': ['layers'] } for layer in module_defs: layer_type = layer['type'] if layer_type in required_params: for param in required_params[layer_type]: assert param in layer, f"{layer_type}层缺少必要参数: {param}" ``` #### 3. 支持多配置文件 ```python # 合并多个配置文件(如主干网络+检测头) def parse_model_config(paths): all_lines = [] for path in paths: with open(path, 'r') as f: all_lines += f.read().split('\n') # 后续解析逻辑不变... ``` --- ### 八、**扩展应用技巧** #### 1. 动态超参数调整 ```python # 根据设备条件自动调整参数 if torch.cuda.is_available(): config = parse_model_config("yolov3-gpu.cfg") else: config = parse_model_config("yolov3-cpu.cfg") ``` #### 2. 自定义层支持 ```python # 添加对新层类型的处理 if module_defs[-1]['type'] == 'my_custom_layer': module_defs[-1]['custom_param'] = 'default_value' ``` 是否需要进一步了解如何将解析后的配置转换为实际的神经网络层实现? 🚀
阅读全文

相关推荐

#下面程序运行地报错: 分析过程中发生错误 启动分析线程失败: 'MultiModalApp' object has no attribute 'update_progress' Traceback (most recent call last): File "D:\PyCharmMiscProject\2025-08-10——Rag高度增能.py", line 1044, in _analyze_image self.analysis_thread.progress_updated.connect(self.update_progress) ^^^^^^^^^^^^^^^^^^^^ AttributeError: 'MultiModalApp' object has no attribute 'update_progress' —————————————————————————————————————————————————————————————————————————————— import sys import os import base64 import json import requests import webbrowser import traceback import psutil from datetime import datetime from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTextEdit, QFileDialog, QGroupBox, QSlider, QDoubleSpinBox, QProgressBar, QSplitter, QMessageBox, QListWidget, QListWidgetItem, QSystemTrayIcon, QMenu, QAction, QComboBox, QTabWidget, QScrollArea, QCheckBox, QInputDialog, QLineEdit, QStyle, QSizePolicy ) from PyQt5.QtGui import ( QPixmap, QFont, QPalette, QColor, QTextCursor, QIcon, QTextDocumentWriter, QTextDocument, QImage, QPainter ) from PyQt5.QtCore import ( Qt, QSize, QThread, pyqtSignal, QTimer, QSettings, QMutex, QCoreApplication, QBuffer ) from PIL import Image, ImageOps # 配置OLLAMA API设置 OLLAMA_HOST = "http://localhost:11434" HISTORY_FILE = "history.json" SETTINGS_FILE = "settings.ini" MAX_COMPRESSED_SIZE = 1024 # 最大压缩尺寸 MAX_THREADS = 2 # 最大并发线程数 # 全局锁防止资源冲突 analysis_mutex = QMutex() class ModelLoaderThread(QThread): models_loaded = pyqtSignal(list) error_occurred = pyqtSignal(str) def run(self): try: response = requests.get(f"{OLLAMA_HOST}/api/tags", timeout=10) response.raise_for_status() models_data = response.json() models = [] for model in models_data.get("models", []): # 保留完整的模型名称(包含冒号) model_name = model["name"] models.append(model_name) self.models_loaded.emit(models) except Exception as e: self.error_occurred.emit(f"模型加载失败: {str(e)}") class ImageAnalysisThread(QThread): analysis_complete = pyqtSignal(str, str) # 结果, 图片路径 progress_updated = pyqtSignal(int) error_occurred = pyqtSignal(str) stream_data = pyqtSignal(str) def __init__(self, model_name, image_path, temperature, max_tokens, prompt, parent=None): super().__init__(parent) self.model_name = model_name self.image_path = image_path self.temperature = temperature self.max_tokens = max_tokens self.prompt = prompt self._is_running = True def run(self): # 获取锁,防止多个线程同时访问资源 analysis_mutex.lock() try: # 检查图片文件是否存在 if not os.path.exists(self.image_path): self.error_occurred.emit(f"图片文件不存在: {self.image_path}") return # 压缩图片 compressed_path = self.compress_image(self.image_path) if not compressed_path: compressed_path = self.image_path # 读取并编码图片为base64 with open(compressed_path, "rb") as image_file: base64_image = base64.b64encode(image_file.read()).decode("utf-8") # 构建请求数据 data = { "model": self.model_name, "prompt": self.prompt, "images": [base64_image], "stream": True, "options": { "temperature": self.temperature, "num_predict": self.max_tokens } } # 发送请求并处理流式响应 response = requests.post( f"{OLLAMA_HOST}/api/generate", json=data, stream=True, timeout=60 ) response.raise_for_status() full_response = "" for line in response.iter_lines(): if not self._is_running: break if line: decoded_line = line.decode("utf-8") try: json_data = json.loads(decoded_line) if "response" in json_data: chunk = json_data["response"] full_response += chunk self.stream_data.emit(chunk) if "done" in json_data and json_data["done"]: break if "error" in json_data: self.error_occurred.emit(json_data["error"]) return except json.JSONDecodeError: self.error_occurred.emit("无效的API响应") return if self._is_running: self.analysis_complete.emit(full_response, self.image_path) except Exception as e: error_msg = f"API调用失败: {str(e)}\n\n{traceback.format_exc()}" self.error_occurred.emit(error_msg) finally: # 确保无论发生什么都会释放锁 analysis_mutex.unlock() # 删除临时压缩文件 if compressed_path != self.image_path and os.path.exists(compressed_path): try: os.remove(compressed_path) except: pass def compress_image(self, image_path): """压缩图片以减小内存占用""" try: # 检查文件大小 file_size = os.path.getsize(image_path) / (1024 * 1024) # MB if file_size < 1: # 小于1MB不需要压缩 return image_path # 打开图片 img = Image.open(image_path) # 如果图片尺寸过大,调整尺寸 if max(img.size) > MAX_COMPRESSED_SIZE: img.thumbnail((MAX_COMPRESSED_SIZE, MAX_COMPRESSED_SIZE), Image.LANCZOS) # 创建临时文件路径 temp_path = f"temp_compressed_{os.path.basename(image_path)}" # 保存压缩后的图片 if image_path.lower().endswith('.png'): img.save(temp_path, optimize=True, quality=85, format='PNG') else: img.save(temp_path, optimize=True, quality=85) return temp_path except Exception as e: print(f"图片压缩失败: {str(e)}") return image_path def stop(self): self._is_running = False class ExportThread(QThread): export_finished = pyqtSignal(str, bool) def __init__(self, content, file_path, format_type, parent=None): super().__init__(parent) self.content = content self.file_path = file_path self.format_type = format_type def run(self): try: if self.format_type == "html": with open(self.file_path, "w", encoding="utf-8") as f: f.write(self.content) elif self.format_type == "txt": with open(self.file_path, "w", encoding="utf-8") as f: f.write(self.content) elif self.format_type == "pdf": doc = QTextDocument() doc.setHtml(self.content) writer = QTextDocumentWriter(self.file_path) writer.write(doc) self.export_finished.emit(self.file_path, True) except Exception as e: self.export_finished.emit(str(e), False) class MultiModalApp(QMainWindow): def __init__(self): super().__init__() self.image_paths = [] self.current_image_index = 0 self.history = [] self.active_threads = 0 self.settings = QSettings(SETTINGS_FILE, QSettings.IniFormat) self.initUI() self.load_settings() self.setWindowTitle("增强版多模态大模型图像解读系统") self.setGeometry(100, 100, 1920, 1000) # 初始化系统托盘 self.init_tray_icon() # 创建资源监控定时器 self.resource_timer = QTimer(self) self.resource_timer.timeout.connect(self.monitor_resources) self.resource_timer.start(5000) # 每5秒监控一次 QTimer.singleShot(500, self.load_models) def initUI(self): # 创建暗色主题样式表 self.setStyleSheet(""" /* 主窗口样式 */ QMainWindow { background-color: #0a192f; } /* 分组框样式 */ QGroupBox { border: 2px solid #64ffda; border-radius: 10px; margin-top: 1ex; color: #ccd6f6; font-weight: bold; } /* 标签样式 */ QLabel { color: #ccd6f6; } /* 按钮样式 */ QPushButton { background-color: #112240; color: #64ffda; border: 1px solid #64ffda; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QPushButton:disabled { background-color: #0d1b30; color: #4a8f7c; border: 1px solid #4a8f7c; } /* 文本框样式 */ QTextEdit { background-color: #0a192f; color: #a8b2d1; border: 1px solid #64ffda; border-radius: 5px; padding: 5px; font-size: 12pt; } /* 选项卡样式 */ QTabWidget::pane { border: 1px solid #64ffda; border-radius: 5px; background: #0a192f; } QTabBar::tab { background: #112240; color: #ccd6f6; padding: 8px; border: 1px solid #64ffda; border-bottom: none; border-top-left-radius: 4px; border-top-right-radius: 4px; } QTabBar::tab:selected { background: #233554; color: #64ffda; } /* 列表样式 */ QListWidget { background-color: #0a192f; color: #a8b2d1; border: 1px solid #64ffda; border-radius: 5px; } QListWidget::item { padding: 5px; } QListWidget::item:selected { background-color: #233554; color: #64ffda; } /* 进度条样式 */ QProgressBar { border: 1px solid #64ffda; border-radius: 5px; text-align: center; background-color: #0a192f; color: #64ffda; } QProgressBar::chunk { background-color: #64ffda; width: 10px; } /* 状态标签样式 */ #statusLabel { color: #64ffda; font-weight: bold; padding: 2px 5px; border-radius: 3px; background-color: #112240; } """) # 设置主窗口布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 标题区域 title_layout = QHBoxLayout() self.title_label = QLabel("增强版多模态大模型图像解读系统") self.title_label.setStyleSheet("font-size: 24pt; font-weight: bold; color: #64ffda;") title_layout.addWidget(self.title_label) # 添加主题切换按钮 self.theme_button = QPushButton("切换主题") self.theme_button.clicked.connect(self.toggle_theme) title_layout.addWidget(self.theme_button) main_layout.addLayout(title_layout) # 主内容区域 splitter = QSplitter(Qt.Horizontal) # 左侧控制面板 left_panel = QWidget() left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(5, 5, 5, 5) # 图片预览区域(改为选项卡形式) self.image_tabs = QTabWidget() self.image_tabs.setTabsClosable(True) self.image_tabs.tabCloseRequested.connect(self.close_image_tab) left_layout.addWidget(self.image_tabs, 3) # 控制面板区域 control_tabs = QTabWidget() # 模型控制选项卡 model_tab = QWidget() self.setup_model_tab(model_tab) control_tabs.addTab(model_tab, "模型设置") # 参数控制选项卡 param_tab = QWidget() self.setup_param_tab(param_tab) control_tabs.addTab(param_tab, "参数设置") # 预设控制选项卡 preset_tab = QWidget() self.setup_preset_tab(preset_tab) control_tabs.addTab(preset_tab, "预设管理") left_layout.addWidget(control_tabs, 2) # 右侧结果面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(5, 5, 5, 5) # 结果展示区域 result_tabs = QTabWidget() # 分析结果选项卡 self.result_tab = QWidget() self.setup_result_tab(self.result_tab) result_tabs.addTab(self.result_tab, "分析结果") # 历史记录选项卡 self.history_tab = QWidget() self.setup_history_tab(self.history_tab) result_tabs.addTab(self.history_tab, "历史记录") right_layout.addWidget(result_tabs) # 添加面板到分割器 splitter.addWidget(left_panel) splitter.addWidget(right_panel) splitter.setSizes([800, 1100]) main_layout.addWidget(splitter) # 状态栏 self.status_bar = self.statusBar() self.status_bar.setStyleSheet("background-color: #112240; color: #64ffda;") # 资源监控标签 self.resource_label = QLabel() self.resource_label.setObjectName("statusLabel") self.status_bar.addPermanentWidget(self.resource_label) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) self.progress_bar.setFixedWidth(200) self.status_bar.addPermanentWidget(self.progress_bar) # 活动线程标签 self.thread_label = QLabel("线程: 0") self.thread_label.setObjectName("statusLabel") self.status_bar.addPermanentWidget(self.thread_label) self.status_bar.showMessage("系统已就绪") def setup_model_tab(self, tab): layout = QVBoxLayout(tab) layout.setContentsMargins(5, 5, 5, 5) # 模型选择区域 model_group = QGroupBox("模型选择") model_layout = QVBoxLayout() self.model_list = QListWidget() self.model_list.setMaximumHeight(150) self.model_list.addItem("正在加载模型...") self.refresh_models_button = QPushButton("刷新模型列表") self.refresh_models_button.clicked.connect(self.load_models) model_layout.addWidget(self.model_list) model_layout.addWidget(self.refresh_models_button) model_group.setLayout(model_layout) layout.addWidget(model_group) # 图片操作区域 image_group = QGroupBox("图片操作") image_layout = QVBoxLayout() self.load_button = QPushButton("加载图片") self.load_button.clicked.connect(self.load_image) self.load_multiple_button = QPushButton("批量加载图片") self.load_multiple_button.clicked.connect(self.load_multiple_images) self.clear_images_button = QPushButton("清除所有图片") self.clear_images_button.clicked.connect(self.clear_all_images) image_layout.addWidget(self.load_button) image_layout.addWidget(self.load_multiple_button) image_layout.addWidget(self.clear_images_button) image_group.setLayout(image_layout) layout.addWidget(image_group) # 分析控制区域 control_group = QGroupBox("分析控制") control_layout = QVBoxLayout() self.analyze_button = QPushButton("分析当前图片") self.analyze_button.clicked.connect(self.analyze_image) self.analyze_button.setEnabled(False) self.analyze_all_button = QPushButton("批量分析所有图片") self.analyze_all_button.clicked.connect(self.analyze_all_images) self.analyze_all_button.setEnabled(False) self.stop_button = QPushButton("停止分析") self.stop_button.clicked.connect(self.stop_analysis) self.stop_button.setEnabled(False) control_layout.addWidget(self.analyze_button) control_layout.addWidget(self.analyze_all_button) control_layout.addWidget(self.stop_button) control_group.setLayout(control_layout) layout.addWidget(control_group) def setup_param_tab(self, tab): layout = QVBoxLayout(tab) layout.setContentsMargins(5, 5, 5, 5) # 温度控制 temp_group = QGroupBox("温度控制") temp_layout = QHBoxLayout() self.temp_slider = QSlider(Qt.Horizontal) self.temp_slider.setRange(0, 100) self.temp_slider.setValue(50) self.temp_value = QDoubleSpinBox() self.temp_value.setRange(0.0, 1.0) self.temp_value.setSingleStep(0.1) self.temp_value.setValue(0.5) self.temp_value.setDecimals(1) self.temp_slider.valueChanged.connect(lambda val: self.temp_value.setValue(val / 100)) self.temp_value.valueChanged.connect(lambda val: self.temp_slider.setValue(int(val * 100))) temp_layout.addWidget(self.temp_slider) temp_layout.addWidget(self.temp_value) temp_group.setLayout(temp_layout) layout.addWidget(temp_group) # Token控制 token_group = QGroupBox("Token控制") token_layout = QHBoxLayout() self.token_spin = QDoubleSpinBox() self.token_spin.setRange(100, 5000) self.token_spin.setValue(1000) self.token_spin.setSingleStep(100) token_layout.addWidget(QLabel("最大Token:")) token_layout.addWidget(self.token_spin) token_group.setLayout(token_layout) layout.addWidget(token_group) # 提示词区域 prompt_group = QGroupBox("提示词设置") prompt_layout = QVBoxLayout() self.prompt_edit = QTextEdit() self.prompt_edit.setPlainText( "请用中文详细描述这张图片的内容,要求描述清晰、有条理,分段落呈现,各段首行按要求缩进2个汉字。") self.prompt_edit.setMinimumHeight(150) prompt_layout.addWidget(self.prompt_edit) prompt_group.setLayout(prompt_layout) layout.addWidget(prompt_group) def setup_preset_tab(self, tab): layout = QVBoxLayout(tab) layout.setContentsMargins(5, 5, 5, 5) # 预设管理 preset_group = QGroupBox("预设管理") preset_layout = QVBoxLayout() self.preset_combo = QComboBox() self.preset_combo.addItems(["默认预设", "详细描述", "创意写作", "技术分析"]) button_layout = QHBoxLayout() self.load_preset_button = QPushButton("加载预设") self.load_preset_button.clicked.connect(self.load_preset) self.save_preset_button = QPushButton("保存预设") self.save_preset_button.clicked.connect(self.save_preset) self.delete_preset_button = QPushButton("删除预设") self.delete_preset_button.clicked.connect(self.delete_preset) button_layout.addWidget(self.load_preset_button) button_layout.addWidget(self.save_preset_button) button_layout.addWidget(self.delete_preset_button) preset_layout.addWidget(self.preset_combo) preset_layout.addLayout(button_layout) preset_group.setLayout(preset_layout) layout.addWidget(preset_group) # 自动保存设置 auto_save_group = QGroupBox("自动保存设置") auto_save_layout = QVBoxLayout() self.auto_save_check = QCheckBox("自动保存分析结果") self.auto_save_check.setChecked(True) self.auto_save_path_button = QPushButton("选择保存路径") self.auto_save_path_button.clicked.connect(self.select_auto_save_path) self.auto_save_path_label = QLabel("默认保存位置: 程序目录/results") self.auto_save_path_label.setWordWrap(True) auto_save_layout.addWidget(self.auto_save_check) auto_save_layout.addWidget(self.auto_save_path_button) auto_save_layout.addWidget(self.auto_save_path_label) auto_save_group.setLayout(auto_save_layout) layout.addWidget(auto_save_group) def setup_result_tab(self, tab): layout = QVBoxLayout(tab) layout.setContentsMargins(5, 5, 5, 5) # 结果展示区域 result_group = QGroupBox("分析结果") result_layout = QVBoxLayout() self.result_edit = QTextEdit() self.result_edit.setReadOnly(True) # 结果操作按钮 button_layout = QHBoxLayout() self.save_result_button = QPushButton("保存结果") self.save_result_button.clicked.connect(self.save_result) self.copy_result_button = QPushButton("复制结果") self.copy_result_button.clicked.connect(self.copy_result) self.clear_result_button = QPushButton("清除结果") self.clear_result_button.clicked.connect(self.clear_results) button_layout.addWidget(self.save_result_button) button_layout.addWidget(self.copy_result_button) button_layout.addWidget(self.clear_result_button) result_layout.addWidget(self.result_edit) result_layout.addLayout(button_layout) result_group.setLayout(result_layout) layout.addWidget(result_group) def setup_history_tab(self, tab): layout = QVBoxLayout(tab) layout.setContentsMargins(5, 5, 5, 5) # 历史记录区域 history_group = QGroupBox("历史记录") history_layout = QVBoxLayout() self.history_list = QListWidget() self.history_list.itemDoubleClicked.connect(self.load_history_item) # 历史操作按钮 button_layout = QHBoxLayout() self.load_history_button = QPushButton("加载历史") self.load_history_button.clicked.connect(self.load_history) self.clear_history_button = QPushButton("清除历史") self.clear_history_button.clicked.connect(self.clear_history) button_layout.addWidget(self.load_history_button) button_layout.addWidget(self.clear_history_button) history_layout.addWidget(self.history_list) history_layout.addLayout(button_layout) history_group.setLayout(history_layout) layout.addWidget(history_group) def init_tray_icon(self): """初始化系统托盘图标""" self.tray_icon = QSystemTrayIcon(self) # 使用系统默认图标 icon = self.style().standardIcon(QStyle.SP_ComputerIcon) self.tray_icon.setIcon(icon) tray_menu = QMenu() show_action = QAction("显示窗口", self) show_action.triggered.connect(self.show_normal) tray_menu.addAction(show_action) exit_action = QAction("退出", self) exit_action.triggered.connect(self.close) tray_menu.addAction(exit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() # 托盘图标点击事件 self.tray_icon.activated.connect(self.tray_icon_clicked) def show_normal(self): """从托盘恢复窗口显示""" self.showNormal() self.activateWindow() def tray_icon_clicked(self, reason): """处理托盘图标点击事件""" if reason == QSystemTrayIcon.DoubleClick: self.show_normal() def toggle_theme(self): """切换主题""" if self.theme_button.text() == "切换主题": # 切换到浅色主题 self.setStyleSheet(""" QMainWindow { background-color: #f5f5f5; } QGroupBox { border: 2px solid #2c3e50; border-radius: 10px; margin-top: 1ex; color: #2c3e50; font-weight: bold; } QLabel { color: #2c3e50; } QPushButton { background-color: #ecf0f1; color: #2c3e50; border: 1px solid #bdc3c7; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QPushButton:disabled { background-color: #d5dbdb; color: #7f8c8d; border: 1px solid #bdc3c7; } QTextEdit { background-color: #ffffff; color: #2c3e50; border: 1px solid #bdc3c7; border-radius: 5px; padding: 5px; font-size: 12pt; } QListWidget { background-color: #ffffff; color: #2c3e50; border: 1px solid #bdc3c7; border-radius: 5px; } QListWidget::item { padding: 5px; } QListWidget::item:selected { background-color: #3498db; color: #ffffff; } QProgressBar { border: 1px solid #2c3e50; border-radius: 5px; text-align: center; background-color: #ecf0f1; color: #2c3e50; } QProgressBar::chunk { background-color: #3498db; width: 10px; } #statusLabel { color: #2c3e50; font-weight: bold; padding: 2px 5px; border-radius: 3px; background-color: #ecf0f1; } """) self.theme_button.setText("切换回暗色") self.title_label.setStyleSheet("font-size: 24pt; font-weight: bold; color: #2c3e50;") self.status_bar.setStyleSheet("background-color: #ecf0f1; color: #2c3e50;") else: # 切换回暗色主题 self.setStyleSheet(""" QMainWindow { background-color: #0a192f; } QGroupBox { border: 2px solid #64ffda; border-radius: 10px; margin-top: 1ex; color: #ccd6f6; font-weight: bold; } QLabel { color: #ccd6f6; } QPushButton { background-color: #112240; color: #64ffda; border: 1px solid #64ffda; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QPushButton:disabled { background-color: #0d1b30; color: #4a8f7c; border: 1px solid #4a8f7c; } QTextEdit { background-color: #0a192f; color: #a8b2d1; border: 1px solid #64ffda; border-radius: 5px; padding: 5px; font-size: 12pt; } QListWidget { background-color: #0a192f; color: #a8b2d1; border: 1px solid #64ffda; border-radius: 5px; } QListWidget::item { padding: 5px; } QListWidget::item:selected { background-color: #233554; color: #64ffda; } QProgressBar { border: 1px solid #64ffda; border-radius: 5px; text-align: center; background-color: #0a192f; color: #64ffda; } QProgressBar::chunk { background-color: #64ffda; width: 10px; } #statusLabel { color: #64ffda; font-weight: bold; padding: 2px 5px; border-radius: 3px; background-color: #112240; } """) self.theme_button.setText("切换主题") self.title_label.setStyleSheet("font-size: 24pt; font-weight: bold; color: #64ffda;") self.status_bar.setStyleSheet("background-color: #112240; color: #64ffda;") def load_settings(self): """加载程序设置""" self.settings.beginGroup("MainWindow") self.restoreGeometry(self.settings.value("geometry", self.saveGeometry())) self.restoreState(self.settings.value("windowState", self.saveState())) self.settings.endGroup() # 加载历史记录 self.load_history() # 加载自动保存路径 auto_save_path = self.settings.value("AutoSave/path", "results") self.auto_save_path_label.setText(f"保存位置: {auto_save_path}") def save_settings(self): """保存程序设置""" self.settings.beginGroup("MainWindow") self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) self.settings.endGroup() # 保存历史记录 self.save_history() def load_models(self): """加载可用模型列表""" self.model_list.clear() self.model_list.addItem("正在加载模型...") self.model_loader_thread = ModelLoaderThread() self.model_loader_thread.models_loaded.connect(self.update_model_list) self.model_loader_thread.error_occurred.connect(self.handle_model_load_error) self.model_loader_thread.start() def update_model_list(self, models): """更新模型列表""" self.model_list.clear() if not models: self.model_list.addItem("没有找到可用模型") return for model in models: # 显示完整的模型名称(包含冒号) item = QListWidgetItem(f"● {model}") item.setData(Qt.UserRole, model) self.model_list.addItem(item) # 默认选择第一个模型 if models: self.model_list.setCurrentRow(0) def handle_model_load_error(self, error_msg): """处理模型加载错误""" self.model_list.clear() self.model_list.addItem(error_msg) self.status_bar.showMessage(error_msg) def load_image(self): """加载单张图片""" file_path, _ = QFileDialog.getOpenFileName( self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif)" ) if file_path: self.add_image_tab(file_path) def load_multiple_images(self): """批量加载多张图片""" file_paths, _ = QFileDialog.getOpenFileNames( self, "选择多张图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif)" ) if file_paths: for file_path in file_paths: self.add_image_tab(file_path) def add_image_tab(self, file_path): """添加图片选项卡""" try: pixmap = QPixmap(file_path) if pixmap.isNull(): raise Exception("无法加载图片文件,可能格式不支持或文件已损坏") scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) image_label = QLabel() image_label.setPixmap(pixmap.scaled( 800, 600, Qt.KeepAspectRatio, Qt.SmoothTransformation )) image_label.setAlignment(Qt.AlignCenter) scroll_area.setWidget(image_label) tab_index = self.image_tabs.addTab( scroll_area, os.path.basename(file_path) ) self.image_tabs.setCurrentIndex(tab_index) self.image_paths.append(file_path) self.analyze_button.setEnabled(True) self.analyze_all_button.setEnabled(True) self.status_bar.showMessage(f"已加载图片: {os.path.basename(file_path)}") # 更新当前图片索引 self.current_image_index = tab_index except Exception as e: self.status_bar.showMessage(f"错误: {str(e)}") self.show_error_dialog("图片加载错误", f"无法加载图片:\n{str(e)}") def close_image_tab(self, index): """关闭图片选项卡""" if index < len(self.image_paths): self.image_paths.pop(index) self.image_tabs.removeTab(index) if not self.image_paths: self.analyze_button.setEnabled(False) self.analyze_all_button.setEnabled(False) def clear_all_images(self): """清除所有图片""" self.image_tabs.clear() self.image_paths.clear() self.analyze_button.setEnabled(False) self.analyze_all_button.setEnabled(False) self.status_bar.showMessage("已清除所有图片") def analyze_image(self): """分析当前图片""" current_index = self.image_tabs.currentIndex() if current_index < 0 or current_index >= len(self.image_paths): self.status_bar.showMessage("错误: 没有可分析的图片") return self.current_image_index = current_index self._analyze_image(self.image_paths[current_index]) def analyze_all_images(self): """批量分析所有图片""" if not self.image_paths: self.status_bar.showMessage("错误: 没有可分析的图片") return # 保存当前索引 saved_index = self.current_image_index # 逐个分析图片 for i, image_path in enumerate(self.image_paths): # 检查活动线程数 while self.active_threads >= MAX_THREADS: QCoreApplication.processEvents() # 处理事件循环 self.current_image_index = i self.image_tabs.setCurrentIndex(i) self._analyze_image(image_path) # 等待分析完成 while hasattr(self, 'analysis_thread') and self.analysis_thread.isRunning(): QApplication.processEvents() # 恢复原始索引 self.current_image_index = saved_index self.image_tabs.setCurrentIndex(saved_index) def _analyze_image(self, image_path): """实际执行图片分析的内部方法""" selected_items = self.model_list.selectedItems() if not selected_items: self.status_bar.showMessage("错误: 请选择模型") return # 获取完整的模型名称(包含冒号) model_name = selected_items[0].data(Qt.UserRole) self.result_edit.clear() self.progress_bar.setValue(0) self.progress_bar.setFormat("正在分析图片...") self.set_buttons_enabled(False) self.stop_button.setEnabled(True) temperature = self.temp_value.value() max_tokens = int(self.token_spin.value()) prompt = self.prompt_edit.toPlainText().strip() # 确保之前的线程已停止 if hasattr(self, 'analysis_thread') and self.analysis_thread.isRunning(): self.analysis_thread.stop() self.analysis_thread.wait(2000) # 等待线程结束 try: self.analysis_thread = ImageAnalysisThread( model_name, image_path, temperature, max_tokens, prompt ) self.analysis_thread.analysis_complete.connect(self.handle_analysis_result) self.analysis_thread.progress_updated.connect(self.update_progress) self.analysis_thread.error_occurred.connect(self.handle_analysis_error) self.analysis_thread.stream_data.connect(self.analysis_stream_data) self.analysis_thread.finished.connect(self.analysis_finished) self.analysis_thread.start() # 更新活动线程计数 self.active_threads += 1 self.thread_label.setText(f"线程: {self.active_threads}") except Exception as e: error_msg = f"启动分析线程失败: {str(e)}\n\n{traceback.format_exc()}" self.handle_analysis_error(error_msg) def analysis_stream_data(self, chunk): """处理流式数据""" cursor = self.result_edit.textCursor() cursor.movePosition(QTextCursor.End) cursor.insertText(chunk) self.result_edit.setTextCursor(cursor) self.result_edit.ensureCursorVisible() def handle_analysis_result(self, result, image_path): """处理分析结果""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") selected_items = self.model_list.selectedItems() model_name = selected_items[0].text()[2:] if selected_items else "未知模型" # 获取当前图片文件名 image_name = os.path.basename(image_path) # 格式化结果 formatted_result = self.format_result(result) result_html = f""" 图片分析结果: {image_name} {formatted_result} 模型: {model_name} | 时间: {timestamp} """ self.result_edit.setHtml(result_html) self.status_bar.showMessage(f"图片分析完成: {image_name}") self.progress_bar.setValue(100) self.progress_bar.setFormat("分析完成") # 添加到历史记录 self.add_to_history(image_name, model_name, timestamp, result_html) # 自动保存结果 if self.auto_save_check.isChecked(): self.auto_save_result(image_name, result_html) def format_result(self, result): """格式化分析结果为HTML""" # 将文本转换为段落 paragraphs = result.split("\n\n") formatted_paragraphs = [] for para in paragraphs: if para.strip(): # 替换缩进空格为HTML空格 formatted_para = para.replace(" ", "  ") formatted_paragraphs.append(f"{formatted_para}") return "\n".join(formatted_paragraphs) def add_to_history(self, image_name, model_name, timestamp, content): """添加到历史记录""" history_item = { "image": image_name, "model": model_name, "time": timestamp, "content": content } self.history.insert(0, history_item) self.update_history_list() def update_history_list(self): """更新历史记录列表""" self.history_list.clear() for item in self.history[:50]: # 最多显示50条历史记录 list_item = QListWidgetItem(f"{item['image']} - {item['time']}") list_item.setData(Qt.UserRole, item) self.history_list.addItem(list_item) def load_history_item(self, item): """加载历史记录项""" history_data = item.data(Qt.UserRole) self.result_edit.setHtml(history_data["content"]) def load_history(self): """从文件加载历史记录""" try: if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, "r", encoding="utf-8") as f: self.history = json.load(f) self.update_history_list() self.status_bar.showMessage(f"已加载 {len(self.history)} 条历史记录") except Exception as e: self.status_bar.showMessage(f"加载历史记录失败: {str(e)}") def save_history(self): """保存历史记录到文件""" try: with open(HISTORY_FILE, "w", encoding="utf-8") as f: json.dump(self.history, f, ensure_ascii=False, indent=2) except Exception as e: self.status_bar.showMessage(f"保存历史记录失败: {str(e)}") def clear_history(self): """清除历史记录""" reply = QMessageBox.question( self, "确认清除", "确定要清除所有历史记录吗? 此操作不可撤销!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.history.clear() self.history_list.clear() self.status_bar.showMessage("已清除所有历史记录") def auto_save_result(self, image_name, content): """自动保存结果""" try: save_dir = self.settings.value("AutoSave/path", "results") if not os.path.exists(save_dir): os.makedirs(save_dir) # 生成文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") base_name = os.path.splitext(image_name)[0] file_name = f"{base_name}_{timestamp}.html" file_path = os.path.join(save_dir, file_name) # 保存文件 with open(file_path, "w", encoding="utf-8") as f: f.write(content) self.status_bar.showMessage(f"结果已自动保存到: {file_path}") except Exception as e: self.status_bar.showMessage(f"自动保存失败: {str(e)}") def select_auto_save_path(self): """选择自动保存路径""" save_dir = QFileDialog.getExistingDirectory( self, "选择自动保存目录", self.settings.value("AutoSave/path", "results") ) if save_dir: self.settings.setValue("AutoSave/path", save_dir) self.auto_save_path_label.setText(f"保存位置: {save_dir}") def save_result(self): """保存结果到文件""" if not self.result_edit.toPlainText(): self.status_bar.showMessage("错误: 没有可保存的内容") return file_path, _ = QFileDialog.getSaveFileName( self, "保存结果", "", "HTML文件 (*.html);;文本文件 (*.txt);;PDF文件 (*.pdf)" ) if file_path: format_type = "html" if file_path.endswith(".txt"): format_type = "txt" elif file_path.endswith(".pdf"): format_type = "pdf" content = self.result_edit.toHtml() if format_type != "txt" else self.result_edit.toPlainText() self.export_thread = ExportThread(content, file_path, format_type) self.export_thread.export_finished.connect(self.handle_export_finished) self.export_thread.start() self.status_bar.showMessage("正在导出结果...") def handle_export_finished(self, message, success): """处理导出完成""" if success: self.status_bar.showMessage(f"结果已保存到: {message}") if message.endswith(".html"): webbrowser.open(message) else: self.status_bar.showMessage(f"导出失败: {message}") self.show_error_dialog("导出错误", f"无法保存结果:\n{message}") def copy_result(self): """复制结果到剪贴板""" self.result_edit.selectAll() self.result_edit.copy() self.status_bar.showMessage("结果已复制到剪贴板") def clear_results(self): """清除结果""" self.result_edit.clear() self.status_bar.showMessage("已清除结果") def load_preset(self): """加载预设""" preset_name = self.preset_combo.currentText() if preset_name == "默认预设": self.temp_value.setValue(0.5) self.token_spin.setValue(1000) self.prompt_edit.setPlainText( "请用中文详细描述这张图片的内容,要求描述清晰、有条理,分段落呈现,各段首行按要求缩进2个汉字。") elif preset_name == "详细描述": self.temp_value.setValue(0.3) self.token_spin.setValue(1500) self.prompt_edit.setPlainText( "请用中文详细描述这张图片中的每一个细节,包括但不限于场景、人物、物体、颜色、空间关系等。要求描述系统全面,层次分明,每段描述一个方面。") elif preset_name == "创意写作": self.temp_value.setValue(0.7) self.token_spin.setValue(2000) self.prompt_edit.setPlainText( "请根据这张图片创作一个富有想象力的故事或诗歌。要求内容生动有趣,语言优美,可以适当发挥想象力,但不要偏离图片内容太远。") elif preset_name == "技术分析": self.temp_value.setValue(0.2) self.token_spin.setValue(1200) self.prompt_edit.setPlainText( "请从技术角度分析这张图片,包括但不限于构图、色彩、光线、透视等专业要素。要求分析专业准确,使用适当的专业术语。") self.status_bar.showMessage(f"已加载预设: {preset_name}") def save_preset(self): """保存当前设置为预设""" preset_name, ok = QInputDialog.getText( self, "保存预设", "请输入预设名称:", QLineEdit.Normal, self.preset_combo.currentText() ) if ok and preset_name: # 检查是否已存在 index = self.preset_combo.findText(preset_name) if index == -1: self.preset_combo.addItem(preset_name) self.preset_combo.setCurrentText(preset_name) self.status_bar.showMessage(f"预设 '{preset_name}' 已保存") def delete_preset(self): """删除预设""" preset_name = self.preset_combo.currentText() if preset_name in ["默认预设", "详细描述", "创意写作", "技术分析"]: QMessageBox.warning(self, "警告", "系统预设不能被删除") return reply = QMessageBox.question( self, "确认删除", f"确定要删除预设 '{preset_name}' 吗?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: index = self.preset_combo.currentIndex() self.preset_combo.removeItem(index) self.status_bar.showMessage(f"预设 '{preset_name}' 已删除") def stop_analysis(self): """停止分析""" if hasattr(self, 'analysis_thread') and self.analysis_thread.isRunning(): self.analysis_thread.stop() self.status_bar.showMessage("分析已停止") self.set_buttons_enabled(True) self.stop_button.setEnabled(False) def analysis_finished(self): """分析完成后的清理工作""" self.set_buttons_enabled(True) self.stop_button.setEnabled(False) # 更新活动线程计数 self.active_threads -= 1 if self.active_threads < 0: self.active_threads = 0 self.thread_label.setText(f"线程: {self.active_threads}") def handle_analysis_error(self, error_msg): """处理分析错误""" # 显示详细的错误信息 self.status_bar.showMessage(f"错误: {error_msg.splitlines()[0]}") self.progress_bar.setFormat("分析失败") self.set_buttons_enabled(True) self.stop_button.setEnabled(False) # 在结果区域显示完整错误信息 error_html = f""" 分析过程中发生错误 {error_msg.replace('\n', '
')}
""" self.result_edit.setHtml(error_html) # 记录错误日志 self.log_error(error_msg) # 更新活动线程计数 self.active_threads -= 1 if self.active_threads < 0: self.active_threads = 0 self.thread_label.setText(f"线程: {self.active_threads}") def log_error(self, error_msg): """记录错误到日志文件""" try: log_dir = "logs" if not os.path.exists(log_dir): os.makedirs(log_dir) log_file = os.path.join(log_dir, "error_log.txt") timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") with open(log_file, "a", encoding="utf-8") as f: f.write(f"\n\n[{timestamp}] 发生错误:\n") f.write(error_msg) f.write("\n" + "-" * 80) except Exception as e: print(f"无法写入错误日志: {str(e)}") def set_buttons_enabled(self, enabled): """设置按钮启用状态""" self.load_button.setEnabled(enabled) self.load_multiple_button.setEnabled(enabled) self.clear_images_button.setEnabled(enabled) self.analyze_button.setEnabled(enabled and len(self.image_paths) > 0) self.analyze_all_button.setEnabled(enabled and len(self.image_paths) > 0) self.model_list.setEnabled(enabled) self.refresh_models_button.setEnabled(enabled) self.preset_combo.setEnabled(enabled) self.load_preset_button.setEnabled(enabled) self.save_preset_button.setEnabled(enabled) self.delete_preset_button.setEnabled(enabled) self.auto_save_check.setEnabled(enabled) self.auto_save_path_button.setEnabled(enabled) self.save_result_button.setEnabled(enabled) self.copy_result_button.setEnabled(enabled) self.clear_result_button.setEnabled(enabled) self.load_history_button.setEnabled(enabled) self.clear_history_button.setEnabled(enabled) def show_error_dialog(self, title, message): """显示错误对话框""" QMessageBox.critical(self, title, message) def monitor_resources(self): """监控系统资源使用情况""" try: cpu_percent = psutil.cpu_percent() memory = psutil.virtual_memory() self.resource_label.setText( f"CPU: {cpu_percent}% | 内存: {memory.percent}%" ) except: pass def closeEvent(self, event): """关闭窗口事件""" # 停止所有运行中的线程 if hasattr(self, 'analysis_thread') and self.analysis_thread.isRunning(): self.analysis_thread.stop() if hasattr(self, 'model_loader_thread') and self.model_loader_thread.isRunning(): self.model_loader_thread.quit() self.model_loader_thread.wait(2000) # 停止资源监控定时器 self.resource_timer.stop() # 保存设置 # 接受关闭事件 event.accept() def handle_exception(exc_type, exc_value, exc_traceback): """全局异常处理函数""" error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback)) print(f"全局异常捕获:\n{error_msg}") # 尝试显示错误对话框 try: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("程序发生未处理异常") msg.setInformativeText(f"{exc_type.__name__}: {exc_value}") msg.setWindowTitle("致命错误") msg.setDetailedText(error_msg) msg.exec_() except: pass # 退出程序 sys.exit(1) if __name__ == "__main__": # 设置全局异常处理 sys.excepthook = handle_exception app = QApplication(sys.argv) window = MultiModalApp() window.show() sys.exit(app.exec_())

# 主要优化点: # 1. 增加了多图片批量处理功能 # 2. 添加了历史记录保存和加载功能 # 3. 实现了结果导出功能(TXT/PDF/HTML) # 4. 优化了UI布局和主题系统 # 5. 增加了模型参数配置预设 # 6. 添加了系统托盘支持 # 7. 实现了结果自动保存 import sys import os import base64 import json import requests import webbrowser from datetime import datetime from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTextEdit, QFileDialog, QGroupBox, QSlider, QDoubleSpinBox, QProgressBar, QSplitter, QMessageBox, QListWidget, QListWidgetItem, QSystemTrayIcon, QMenu, QAction, QComboBox, QTabWidget, QScrollArea, QCheckBox) from PyQt5.QtGui import (QPixmap, QFont, QPalette, QColor, QTextCursor, QIcon, QTextDocumentWriter, QTextDocument) from PyQt5.QtCore import Qt, QSize, QThread, pyqtSignal, QTimer, QSettings # 配置OLLAMA API设置 OLLAMA_HOST = "http://localhost:11434" HISTORY_FILE = "history.json" SETTINGS_FILE = "settings.ini" class EnhancedListWidgetItem(QListWidgetItem): """增强的列表项,支持图标和状态显示""" def __init__(self, text, is_title=False, icon=None, parent=None): super().__init__(text, parent) self.is_title = is_title if icon: self.setIcon(icon) if is_title: self.setSizeHint(QSize(200, 35)) font = QFont("Microsoft YaHei UI", 10, QFont.Bold) self.setFont(font) self.setForeground(QColor("#64ffda")) self.setFlags(Qt.NoItemFlags) self.setBackground(QColor("#112240")) class ModelLoaderThread(QThread): # ...保持原有实现... class ImageAnalysisThread(QThread): # ...保持原有实现... class ExportThread(QThread): export_finished = pyqtSignal(str, bool) def __init__(self, content, file_path, format_type, parent=None): super().__init__(parent) self.content = content self.file_path = file_path self.format_type = format_type def run(self): try: if self.format_type == "html": with open(self.file_path, "w", encoding="utf-8") as f: f.write(self.content) elif self.format_type == "txt": with open(self.file_path, "w", encoding="utf-8") as f: f.write(self.content) elif self.format_type == "pdf": doc = QTextDocument() doc.setHtml(self.content) writer = QTextDocumentWriter(self.file_path) writer.write(doc) self.export_finished.emit(self.file_path, True) except Exception as e: self.export_finished.emit(str(e), False) class MultiModalApp(QMainWindow): def __init__(self): super().__init__() self.image_paths = [] self.current_image_index = 0 self.history = [] self.settings = QSettings(SETTINGS_FILE, QSettings.IniFormat) self.initUI() self.load_settings() self.setWindowTitle("增强版多模态大模型图像解读系统") self.setGeometry(100, 100, 1920, 1000) # 初始化系统托盘 self.init_tray_icon() QTimer.singleShot(500, self.load_models) def initUI(self): # 创建暗色主题样式表 self.setStyleSheet(""" /* 主窗口样式 */ QMainWindow { background-color: #0a192f; } /* 分组框样式 */ QGroupBox { border: 2px solid #64ffda; border-radius: 10px; margin-top: 1ex; color: #ccd6f6; font-weight: bold; } /* 标签样式 */ QLabel { color: #ccd6f6; } /* 按钮样式 */ QPushButton { background-color: #112240; color: #64ffda; border: 1px solid #64ffda; border-radius: 5px; padding: 5px 10px; font-weight: bold; } /* 文本框样式 */ QTextEdit { background-color: #0a192f; color: #a8b2d1; border: 1px solid #64ffda; border-radius: 5px; padding: 5px; font-size: 12pt; } /* 选项卡样式 */ QTabWidget::pane { border: 1px solid #64ffda; border-radius: 5px; background: #0a192f; } QTabBar::tab { background: #112240; color: #ccd6f6; padding: 8px; border: 1px solid #64ffda; border-bottom: none; border-top-left-radius: 4px; border-top-right-radius: 4px; } QTabBar::tab:selected { background: #233554; color: #64ffda; } """) # 设置主窗口布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 标题区域 title_layout = QHBoxLayout() self.title_label = QLabel("增强版多模态大模型图像解读系统") self.title_label.setStyleSheet("font-size: 24pt; font-weight: bold; color: #64ffda;") title_layout.addWidget(self.title_label) # 添加主题切换按钮 self.theme_button = QPushButton("切换主题") self.theme_button.clicked.connect(self.toggle_theme) title_layout.addWidget(self.theme_button) main_layout.addLayout(title_layout) # 主内容区域 splitter = QSplitter(Qt.Horizontal) # 左侧控制面板 left_panel = QWidget() left_layout = QVBoxLayout(left_panel) # 图片预览区域(改为选项卡形式) self.image_tabs = QTabWidget() self.image_tabs.setTabsClosable(True) self.image_tabs.tabCloseRequested.connect(self.close_image_tab) left_layout.addWidget(self.image_tabs) # 控制面板区域 control_tabs = QTabWidget() # 模型控制选项卡 model_tab = QWidget() self.setup_model_tab(model_tab) control_tabs.addTab(model_tab, "模型设置") # 参数控制选项卡 param_tab = QWidget() self.setup_param_tab(param_tab) control_tabs.addTab(param_tab, "参数设置") # 预设控制选项卡 preset_tab = QWidget() self.setup_preset_tab(preset_tab) control_tabs.addTab(preset_tab, "预设管理") left_layout.addWidget(control_tabs) # 右侧结果面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) # 结果展示区域 result_tabs = QTabWidget() # 分析结果选项卡 self.result_tab = QWidget() self.setup_result_tab(self.result_tab) result_tabs.addTab(self.result_tab, "分析结果") # 历史记录选项卡 self.history_tab = QWidget() self.setup_history_tab(self.history_tab) result_tabs.addTab(self.history_tab, "历史记录") right_layout.addWidget(result_tabs) # 添加面板到分割器 splitter.addWidget(left_panel) splitter.addWidget(right_panel) splitter.setSizes([800, 1100]) main_layout.addWidget(splitter) # 状态栏 self.status_bar = self.statusBar() self.status_bar.setStyleSheet("background-color: #112240; color: #64ffda;") self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) self.status_bar.addPermanentWidget(self.progress_bar) self.status_bar.showMessage("系统已就绪") def setup_model_tab(self, tab): layout = QVBoxLayout(tab) # 模型选择区域 model_group = QGroupBox("模型选择") model_layout = QVBoxLayout() self.model_list = QListWidget() self.model_list.setMaximumHeight(150) self.model_list.addItem("正在加载模型...") self.refresh_models_button = QPushButton("刷新模型列表") self.refresh_models_button.clicked.connect(self.load_models) model_layout.addWidget(self.model_list) model_layout.addWidget(self.refresh_models_button) model_group.setLayout(model_layout) layout.addWidget(model_group) # 图片操作区域 image_group = QGroupBox("图片操作") image_layout = QVBoxLayout() self.load_button = QPushButton("加载图片") self.load_button.clicked.connect(self.load_image) self.load_multiple_button = QPushButton("批量加载图片") self.load_multiple_button.clicked.connect(self.load_multiple_images) self.clear_images_button = QPushButton("清除所有图片") self.clear_images_button.clicked.connect(self.clear_all_images) image_layout.addWidget(self.load_button) image_layout.addWidget(self.load_multiple_button) image_layout.addWidget(self.clear_images_button) image_group.setLayout(image_layout) layout.addWidget(image_group) # 分析控制区域 control_group = QGroupBox("分析控制") control_layout = QVBoxLayout() self.analyze_button = QPushButton("分析当前图片") self.analyze_button.clicked.connect(self.analyze_image) self.analyze_button.setEnabled(False) self.analyze_all_button = QPushButton("批量分析所有图片") self.analyze_all_button.clicked.connect(self.analyze_all_images) self.analyze_all_button.setEnabled(False) self.stop_button = QPushButton("停止分析") self.stop_button.clicked.connect(self.stop_analysis) self.stop_button.setEnabled(False) control_layout.addWidget(self.analyze_button) control_layout.addWidget(self.analyze_all_button) control_layout.addWidget(self.stop_button) control_group.setLayout(control_layout) layout.addWidget(control_group) def setup_param_tab(self, tab): layout = QVBoxLayout(tab) # 温度控制 temp_group = QGroupBox("温度控制") temp_layout = QHBoxLayout() self.temp_slider = QSlider(Qt.Horizontal) self.temp_slider.setRange(0, 100) self.temp_slider.setValue(50) self.temp_value = QDoubleSpinBox() self.temp_value.setRange(0.0, 1.0) self.temp_value.setSingleStep(0.1) self.temp_value.setValue(0.5) self.temp_value.setDecimals(1) self.temp_slider.valueChanged.connect(lambda val: self.temp_value.setValue(val / 100)) self.temp_value.valueChanged.connect(lambda val: self.temp_slider.setValue(int(val * 100))) temp_layout.addWidget(self.temp_slider) temp_layout.addWidget(self.temp_value) temp_group.setLayout(temp_layout) layout.addWidget(temp_group) # Token控制 token_group = QGroupBox("Token控制") token_layout = QHBoxLayout() self.token_spin = QDoubleSpinBox() self.token_spin.setRange(100, 5000) self.token_spin.setValue(1000) self.token_spin.setSingleStep(100) token_layout.addWidget(QLabel("最大Token:")) token_layout.addWidget(self.token_spin) token_group.setLayout(token_layout) layout.addWidget(token_group) # 提示词区域 prompt_group = QGroupBox("提示词设置") prompt_layout = QVBoxLayout() self.prompt_edit = QTextEdit() self.prompt_edit.setPlainText("请用中文详细描述这张图片的内容,要求描述清晰、有条理,分段落呈现,各段首行按要求缩进2个汉字。") prompt_layout.addWidget(self.prompt_edit) prompt_group.setLayout(prompt_layout) layout.addWidget(prompt_group) def setup_preset_tab(self, tab): layout = QVBoxLayout(tab) # 预设管理 preset_group = QGroupBox("预设管理") preset_layout = QVBoxLayout() self.preset_combo = QComboBox() self.preset_combo.addItems(["默认预设", "详细描述", "创意写作", "技术分析"]) button_layout = QHBoxLayout() self.load_preset_button = QPushButton("加载预设") self.load_preset_button.clicked.connect(self.load_preset) self.save_preset_button = QPushButton("保存预设") self.save_preset_button.clicked.connect(self.save_preset) self.delete_preset_button = QPushButton("删除预设") self.delete_preset_button.clicked.connect(self.delete_preset) button_layout.addWidget(self.load_preset_button) button_layout.addWidget(self.save_preset_button) button_layout.addWidget(self.delete_preset_button) preset_layout.addWidget(self.preset_combo) preset_layout.addLayout(button_layout) preset_group.setLayout(preset_layout) layout.addWidget(preset_group) # 自动保存设置 auto_save_group = QGroupBox("自动保存设置") auto_save_layout = QVBoxLayout() self.auto_save_check = QCheckBox("自动保存分析结果") self.auto_save_check.setChecked(True) self.auto_save_path_button = QPushButton("选择保存路径") self.auto_save_path_button.clicked.connect(self.select_auto_save_path) self.auto_save_path_label = QLabel("默认保存位置: 程序目录/results") self.auto_save_path_label.setWordWrap(True) auto_save_layout.addWidget(self.auto_save_check) auto_save_layout.addWidget(self.auto_save_path_button) auto_save_layout.addWidget(self.auto_save_path_label) auto_save_group.setLayout(auto_save_layout) layout.addWidget(auto_save_group) def setup_result_tab(self, tab): layout = QVBoxLayout(tab) # 结果展示区域 result_group = QGroupBox("分析结果") result_layout = QVBoxLayout() self.result_edit = QTextEdit() self.result_edit.setReadOnly(True) # 结果操作按钮 button_layout = QHBoxLayout() self.save_result_button = QPushButton("保存结果") self.save_result_button.clicked.connect(self.save_result) self.copy_result_button = QPushButton("复制结果") self.copy_result_button.clicked.connect(self.copy_result) self.clear_result_button = QPushButton("清除结果") self.clear_result_button.clicked.connect(self.clear_results) button_layout.addWidget(self.save_result_button) button_layout.addWidget(self.copy_result_button) button_layout.addWidget(self.clear_result_button) result_layout.addWidget(self.result_edit) result_layout.addLayout(button_layout) result_group.setLayout(result_layout) layout.addWidget(result_group) def setup_history_tab(self, tab): layout = QVBoxLayout(tab) # 历史记录区域 history_group = QGroupBox("历史记录") history_layout = QVBoxLayout() self.history_list = QListWidget() self.history_list.itemDoubleClicked.connect(self.load_history_item) # 历史操作按钮 button_layout = QHBoxLayout() self.load_history_button = QPushButton("加载历史") self.load_history_button.clicked.connect(self.load_history) self.clear_history_button = QPushButton("清除历史") self.clear_history_button.clicked.connect(self.clear_history) button_layout.addWidget(self.load_history_button) button_layout.addWidget(self.clear_history_button) history_layout.addWidget(self.history_list) history_layout.addLayout(button_layout) history_group.setLayout(history_layout) layout.addWidget(history_group) def init_tray_icon(self): """初始化系统托盘图标""" self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon(":/icons/app_icon.png")) tray_menu = QMenu() show_action = QAction("显示窗口", self) show_action.triggered.connect(self.show_normal) tray_menu.addAction(show_action) exit_action = QAction("退出", self) exit_action.triggered.connect(self.close) tray_menu.addAction(exit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() # 托盘图标点击事件 self.tray_icon.activated.connect(self.tray_icon_clicked) def show_normal(self): """从托盘恢复窗口显示""" self.showNormal() self.activateWindow() def tray_icon_clicked(self, reason): """处理托盘图标点击事件""" if reason == QSystemTrayIcon.DoubleClick: self.show_normal() def toggle_theme(self): """切换主题""" if self.theme_button.text() == "切换主题": # 切换到浅色主题 self.setStyleSheet(""" QMainWindow { background-color: #f5f5f5; } QGroupBox { border: 2px solid #2c3e50; border-radius: 10px; margin-top: 1ex; color: #2c3e50; font-weight: bold; } QLabel { color: #2c3e50; } QPushButton { background-color: #ecf0f1; color: #2c3e50; border: 1px solid #bdc3c7; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QTextEdit { background-color: #ffffff; color: #2c3e50; border: 1px solid #bdc3c7; border-radius: 5px; padding: 5px; font-size: 12pt; } """) self.theme_button.setText("切换回暗色") self.title_label.setStyleSheet("font-size: 24pt; font-weight: bold; color: #2c3e50;") self.status_bar.setStyleSheet("background-color: #ecf0f1; color: #2c3e50;") else: # 切换回暗色主题 self.setStyleSheet(""" QMainWindow { background-color: #0a192f; } QGroupBox { border: 2px solid #64ffda; border-radius: 10px; margin-top: 1ex; color: #ccd6f6; font-weight: bold; } QLabel { color: #ccd6f6; } QPushButton { background-color: #112240; color: #64ffda; border: 1px solid #64ffda; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QTextEdit { background-color: #0a192f; color: #a8b2d1; border: 1px solid #64ffda; border-radius: 5px; padding: 5px; font-size: 12pt; } """) self.theme_button.setText("切换主题") self.title_label.setStyleSheet("font-size: 24pt; font-weight: bold; color: #64ffda;") self.status_bar.setStyleSheet("background-color: #112240; color: #64ffda;") def load_settings(self): """加载程序设置""" self.settings.beginGroup("MainWindow") self.restoreGeometry(self.settings.value("geometry", self.saveGeometry())) self.restoreState(self.settings.value("windowState", self.saveState())) self.settings.endGroup() # 加载历史记录 self.load_history() def save_settings(self): """保存程序设置""" self.settings.beginGroup("MainWindow") self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) self.settings.endGroup() # 保存历史记录 self.save_history() def load_image(self): """加载单张图片""" file_path, _ = QFileDialog.getOpenFileName( self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif)" ) if file_path: self.add_image_tab(file_path) def load_multiple_images(self): """批量加载多张图片""" file_paths, _ = QFileDialog.getOpenFileNames( self, "选择多张图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif)" ) if file_paths: for file_path in file_paths: self.add_image_tab(file_path) def add_image_tab(self, file_path): """添加图片选项卡""" try: pixmap = QPixmap(file_path) if pixmap.isNull(): raise Exception("无法加载图片文件,可能格式不支持或文件已损坏") scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) image_label = QLabel() image_label.setPixmap(pixmap.scaled( scroll_area.width(), scroll_area.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation )) image_label.setAlignment(Qt.AlignCenter) scroll_area.setWidget(image_label) tab_index = self.image_tabs.addTab( scroll_area, os.path.basename(file_path) ) self.image_tabs.setCurrentIndex(tab_index) self.image_paths.append(file_path) self.analyze_button.setEnabled(True) self.analyze_all_button.setEnabled(True) self.status_bar.showMessage(f"已加载图片: {os.path.basename(file_path)}") # 更新当前图片索引 self.current_image_index = tab_index except Exception as e: self.status_bar.showMessage(f"错误: {str(e)}") self.show_error_dialog("图片加载错误", f"无法加载图片:\n{str(e)}") def close_image_tab(self, index): """关闭图片选项卡""" if index < len(self.image_paths): self.image_paths.pop(index) self.image_tabs.removeTab(index) if not self.image_paths: self.analyze_button.setEnabled(False) self.analyze_all_button.setEnabled(False) def clear_all_images(self): """清除所有图片""" self.image_tabs.clear() self.image_paths.clear() self.analyze_button.setEnabled(False) self.analyze_all_button.setEnabled(False) self.status_bar.showMessage("已清除所有图片") def analyze_image(self): """分析当前图片""" current_index = self.image_tabs.currentIndex() if current_index < 0 or current_index >= len(self.image_paths): self.status_bar.showMessage("错误: 没有可分析的图片") return self.current_image_index = current_index self._analyze_image(self.image_paths[current_index]) def analyze_all_images(self): """批量分析所有图片""" if not self.image_paths: self.status_bar.showMessage("错误: 没有可分析的图片") return # 保存当前索引 saved_index = self.current_image_index # 逐个分析图片 for i, image_path in enumerate(self.image_paths): self.current_image_index = i self.image_tabs.setCurrentIndex(i) self._analyze_image(image_path) # 等待分析完成 while self.analysis_thread and self.analysis_thread.isRunning(): QApplication.processEvents() # 恢复原始索引 self.current_image_index = saved_index self.image_tabs.setCurrentIndex(saved_index) def _analyze_image(self, image_path): """实际执行图片分析的内部方法""" selected_items = self.model_list.selectedItems() if not selected_items: self.status_bar.showMessage("错误: 请选择模型") return model_name = selected_items[0].data(Qt.UserRole) self.result_edit.clear() self.progress_bar.setValue(0) self.progress_bar.setFormat("正在分析图片...") self.set_buttons_enabled(False) self.stop_button.setEnabled(True) temperature = self.temp_value.value() max_tokens = int(self.token_spin.value()) prompt = self.prompt_edit.toPlainText().strip() self.analysis_thread = ImageAnalysisThread( model_name, image_path, temperature, max_tokens, prompt ) self.analysis_thread.analysis_complete.connect(self.handle_analysis_result) self.analysis_thread.progress_updated.connect(self.update_progress) self.analysis_thread.error_occurred.connect(self.handle_analysis_error) self.analysis_thread.stream_data.connect(self.analysis_stream_data) self.analysis_thread.finished.connect(self.analysis_finished) self.analysis_thread.start() def handle_analysis_result(self, result): """处理分析结果""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") selected_items = self.model_list.selectedItems() model_name = selected_items[0].text()[2:] if selected_items else "未知模型" # 获取当前图片文件名 image_name = os.path.basename(self.image_paths[self.current_image_index]) # 格式化结果 formatted_result = self.format_result(result) result_html = f""" 图片分析结果: {image_name} {formatted_result} 模型: {model_name} | 时间: {timestamp} """ self.result_edit.setHtml(result_html) self.status_bar.showMessage(f"图片分析完成: {image_name}") # 添加到历史记录 self.add_to_history(image_name, model_name, timestamp, result_html) # 自动保存结果 if self.auto_save_check.isChecked(): self.auto_save_result(image_name, result_html) def add_to_history(self, image_name, model_name, timestamp, content): """添加到历史记录""" history_item = { "image": image_name, "model": model_name, "time": timestamp, "content": content } self.history.insert(0, history_item) self.update_history_list() def update_history_list(self): """更新历史记录列表""" self.history_list.clear() for item in self.history[:50]: # 最多显示50条历史记录 list_item = QListWidgetItem(f"{item['image']} - {item['time']}") list_item.setData(Qt.UserRole, item) self.history_list.addItem(list_item) def load_history_item(self, item): """加载历史记录项""" history_data = item.data(Qt.UserRole) self.result_edit.setHtml(history_data["content"]) def load_history(self): """从文件加载历史记录""" try: if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, "r", encoding="utf-8") as f: self.history = json.load(f) self.update_history_list() except Exception as e: print(f"加载历史记录失败: {str(e)}") def save_history(self): """保存历史记录到文件""" try: with open(HISTORY_FILE, "w", encoding="utf-8") as f: json.dump(self.history, f, ensure_ascii=False, indent=2) except Exception as e: print(f"保存历史记录失败: {str(e)}") def clear_history(self): """清除历史记录""" self.history.clear() self.history_list.clear() def auto_save_result(self, image_name, content): """自动保存结果""" try: save_dir = self.settings.value("AutoSave/path", "results") if not os.path.exists(save_dir): os.makedirs(save_dir) # 生成文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") base_name = os.path.splitext(image_name)[0] file_name = f"{base_name}_{timestamp}.html" file_path = os.path.join(save_dir, file_name) # 保存文件 with open(file_path, "w", encoding="utf-8") as f: f.write(content) self.status_bar.showMessage(f"结果已自动保存到: {file_path}") except Exception as e: print(f"自动保存失败: {str(e)}") def select_auto_save_path(self): """选择自动保存路径""" save_dir = QFileDialog.getExistingDirectory( self, "选择自动保存目录", self.settings.value("AutoSave/path", "results") ) if save_dir: self.settings.setValue("AutoSave/path", save_dir) self.auto_save_path_label.setText(f"保存位置: {save_dir}") def save_result(self): """保存结果到文件""" if not self.result_edit.toPlainText(): self.status_bar.showMessage("错误: 没有可保存的内容") return file_path, _ = QFileDialog.getSaveFileName( self, "保存结果", "", "HTML文件 (*.html);;文本文件 (*.txt);;PDF文件 (*.pdf)" ) if file_path: format_type = "html" if file_path.endswith(".txt"): format_type = "txt" elif file_path.endswith(".pdf"): format_type = "pdf" content = self.result_edit.toHtml() if format_type != "txt" else self.result_edit.toPlainText() self.export_thread = ExportThread(content, file_path, format_type) self.export_thread.export_finished.connect(self.handle_export_finished) self.export_thread.start() self.status_bar.showMessage("正在导出结果...") def handle_export_finished(self, message, success): """处理导出完成""" if success: self.status_bar.showMessage(f"结果已保存到: {message}") if message.endswith(".html"): webbrowser.open(message) else: self.status_bar.showMessage(f"导出失败: {message}") self.show_error_dialog("导出错误", f"无法保存结果:\n{message}") def copy_result(self): """复制结果到剪贴板""" self.result_edit.selectAll() self.result_edit.copy() self.status_bar.showMessage("结果已复制到剪贴板") def clear_results(self): """清除结果""" self.result_edit.clear() self.status_bar.showMessage("已清除结果") def load_preset(self): """加载预设""" preset_name = self.preset_combo.currentText() if preset_name == "默认预设": self.temp_value.setValue(0.5) self.token_spin.setValue(1000) self.prompt_edit.setPlainText("请用中文详细描述这张图片的内容,要求描述清晰、有条理,分段落呈现,各段首行按要求缩进2个汉字。") elif preset_name == "详细描述": self.temp_value.setValue(0.3) self.token_spin.setValue(1500) self.prompt_edit.setPlainText("请用中文详细描述这张图片中的每一个细节,包括但不限于场景、人物、物体、颜色、空间关系等。要求描述系统全面,层次分明,每段描述一个方面。") elif preset_name == "创意写作": self.temp_value.setValue(0.7) self.token_spin.setValue(2000) self.prompt_edit.setPlainText("请根据这张图片创作一个富有想象力的故事或诗歌。要求内容生动有趣,语言优美,可以适当发挥想象力,但不要偏离图片内容太远。") elif preset_name == "技术分析": self.temp_value.setValue(0.2) self.token_spin.setValue(1200) self.prompt_edit.setPlainText("请从技术角度分析这张图片,包括但不限于构图、色彩、光线、透视等专业要素。要求分析专业准确,使用适当的专业术语。") self.status_bar.showMessage(f"已加载预设: {preset_name}") def save_preset(self): """保存当前设置为预设""" preset_name, ok = QInputDialog.getText( self, "保存预设", "请输入预设名称:", QLineEdit.Normal, self.preset_combo.currentText() ) if ok and preset_name: # 检查是否已存在 index = self.preset_combo.findText(preset_name) if index == -1: self.preset_combo.addItem(preset_name) self.preset_combo.setCurrentText(preset_name) self.status_bar.showMessage(f"预设 '{preset_name}' 已保存") def delete_preset(self): """删除预设""" preset_name = self.preset_combo.currentText() if preset_name in ["默认预设", "详细描述", "创意写作", "技术分析"]: QMessageBox.warning(self, "警告", "系统预设不能被删除") return reply = QMessageBox.question( self, "确认删除", f"确定要删除预设 '{preset_name}' 吗?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: index = self.preset_combo.currentIndex() self.preset_combo.removeItem(index) self.status_bar.showMessage(f"预设 '{preset_name}' 已删除") def closeEvent(self, event): """关闭窗口事件""" if self.analysis_thread and self.analysis_thread.isRunning(): self.analysis_thread.stop() if self.model_loader_thread and self.model_loader_thread.isRunning(): self.model_loader_thread.terminate() self.model_loader_thread.wait(2000) self.save_settings() event.accept() if __name__ == "__main__": def exception_handler(exctype, value, traceback): error_msg = f"程序发生未捕获的异常:\n\n类型: {exctype.__name__}\n\n描述: {value}" print(error_msg) try: app = QApplication.instance() if app is not None: msg = QMessageBox()

import subprocess import os import tkinter as tk from tkinter import messagebox import time import pyautogui def create_sandbox(sandbox_name): # Sandboxie 的配置工具路径 sbieini_path = r"D:\Program Files (x86)\沙盒\Sandboxie-Plus\SbieIni.exe" # 检查 SbieIni.exe 是否存在 if not os.path.exists(sbieini_path): print(f"未找到 Sandboxie 配置工具:{sbieini_path}") return False # 创建沙盒的命令 command = [sbieini_path, "set", sandbox_name, "Enabled", "y"] try: subprocess.run(command, check=True) print(f"沙盒 '{sandbox_name}' 创建成功。") return True except subprocess.CalledProcessError as e: print(f"创建沙盒 '{sandbox_name}' 失败:{e}") return False def run_steam_in_sandbox(sandbox_name, username, password): # Sandboxie 的启动程序路径 sandboxie_path = r"D:\Program Files (x86)\沙盒\Sandboxie-Plus\Start.exe" # Steam 客户端的安装路径 steam_path = r"D:\Program Files (x86)\Steam\Steam.exe" # 检查 Sandboxie 启动程序和 Steam 客户端是否存在 if not os.path.exists(sandboxie_path): print(f"未找到 Sandboxie 启动程序:{sandboxie_path}") return if not os.path.exists(steam_path): print(f"未找到 Steam 客户端:{steam_path}") return # 创建 Sandboxie 命令 command = [sandboxie_path, f'/box:{sandbox_name}', steam_path, '-login', username, password] try: # 启动 Steam 客户端 process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) print(f"Steam 客户端已在沙盒 '{sandbox_name}' 中启动。") # 等待并处理输出 time.sleep(10) # 等待 Steam 加载登录页面 # 模拟按键(如果需要),例如按 Enter 键 pyautogui.press('enter') # 如果卡住了,按一下 Enter 键看看能否继续 while True: output = process.stdout.readline() if output == '' and process.poll() is not None: break if output: print(output.strip()) # 检查是否需要验证码 if '请输入验证码' in output: print("检测到验证码请求,请输入验证码") code = input('请输入验证码并按回车:') process.stdin.write(code + '\n') process.stdin.flush() break # 停止等待,并继续登录 # 如果验证码成功输入,等待Steam登录 time.sleep(10) # 等待 10 秒,以确保登录过程完成 except subprocess.CalledProcessError as e: print(f"在沙盒 '{sandbox_name}' 中启动 Steam 失败:{e}") def start_batch_login(): # 获取输入框中的账号和密码 accounts = [] input_text = account_textbox.get("1.0", "end-1c").strip() # 获取多行输入文本 if not input_text: messagebox.showerror("错误", "请输入账号和密码!") return # 解析每行输入的账号密码(假设账号和密码通过 ---- 分隔) lines = input_text.splitlines() for line in lines: if "----" not in line: messagebox.showerror("错误", f"无效的账号密码格式:{line}") return username, password = line.split("----") username = username.strip() password = password.strip() if not username or not password: messagebox.showerror("错误", "账号和密码不能为空!") return accounts.append((username, password)) # 批量创建沙盒并启动 Steam 客户端 for i, (username, password) in enumerate(accounts): sandbox_name = f"SteamBox{i + 1}" if create_sandbox(sandbox_name): run_steam_in_sandbox(sandbox_name, username, password) # 创建 Tkinter 窗口 window = tk.Tk() window.title("Steam 批量自动登录") # 提示标签 label_instruction = tk.Label(window, text="请输入账号密码,每行一个,账号和密码用 '----' 分隔:") label_instruction.grid(row=0, column=0, columnspan=2, padx=10, pady=10) # 多行文本框,用户输入账号密码 account_textbox = tk.Text(window, height=10, width=50) account_textbox.grid(row=1, column=0, columnspan=2, padx=10, pady=10) # 登录按钮 login_button = tk.Button(window, text="批量登录并启动 Steam", command=start_batch_login) login_button.grid(row=2, columnspan=2, padx=10, pady=20) # 启动 Tkinter 主循环 window.mainloop() 我这段代码中利用沙盒实现steam批量自动登录,现在出现问题,有需要登录验证的steam账号密码登录不上去,不弹出输入验证,而没有开启登录验证的可以直接登录

C:\Users\陈柯嘉>pip install pandas Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting pandas Downloading https://pypi.tuna.tsinghua.edu.cn/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz (4.4 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.4/4.4 MB 20.4 MB/s eta 0:00:00 Installing build dependencies ... done Getting requirements to build wheel ... done Installing backend dependencies ... done Preparing metadata (pyproject.toml) ... error error: subprocess-exited-with-error × Preparing metadata (pyproject.toml) did not run successfully. │ exit code: 2 ╰─> [32 lines of output] + meson setup C:\Users\陈柯嘉\AppData\Local\Temp\pip-install-1zmbipo9\pandas_f3dee660ca06481fa639fc991b6efdb4 C:\Users\陈柯嘉\AppData\Local\Temp\pip-install-1zmbipo9\pandas_f3dee660ca06481fa639fc991b6efdb4\.mesonpy-bg46k2qm\build -Dbuildtype=release -Db_ndebug=if-release -Db_vscrt=md --vsenv --native-file=C:\Users\陈柯嘉\AppData\Local\Temp\pip-install-1zmbipo9\pandas_f3dee660ca06481fa639fc991b6efdb4\.mesonpy-bg46k2qm\build\meson-python-native-file.ini Traceback (most recent call last): File "C:\Users\陈柯嘉\AppData\Local\Temp\pip-build-env-9lvekt6r\overlay\Lib\site-packages\mesonbuild\mesonmain.py", line 194, in run return options.run_func(options) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\陈柯嘉\AppData\Local\Temp\pip-build-env-9lvekt6r\overlay\Lib\site-packages\mesonbuild\msetup.py", line 358, in run app.generate() File "C:\Users\陈柯嘉\AppData\Local\Temp\pip-build-env-9lvekt6r\overlay\Lib\site-packages\mesonbuild\msetup.py", line 178, in generate env = environment.Environment(self.source_dir, self.build_dir, self.options) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\陈柯嘉\AppData\Local\Temp\pip-build-env-9lvekt6r\overlay\Lib\site-packages\mesonbuild\environment.py", line 552, in __init__ config = coredata.parse_machine_files(self.coredata.config_files) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\陈柯嘉\AppData\Local\Temp\pip-build-env-9lvekt6r\overlay\Lib\site-packages\mesonbuild\coredata.py", line 1030, in parse_machine_files parser = MachineFileParser(filenames) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\陈柯嘉\AppData\Local\Temp\pip-build-env-9lvekt6r\overlay\Lib\site-packages\mesonbuild\coredata.py", line 972, in __init__ self.parser.read(filenames) File "C:\Users\陈柯嘉\AppData\Local\Temp\pip-build-env-9lvekt6r\overlay\Lib\site-packages\mesonbuild\coredata.py", line 959, in read return super().read(filenames, encoding) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\陈柯嘉\AppData\Local\Programs\Python\Python312-32\Lib\configparser.py", line 684, in read self._read(fp, filename) File "C:\Users\陈柯嘉\AppData\Local\Programs\Python\Python312-32\Lib\configparser.py", line 999, in _read for lineno, line in enumerate(fp, start=1): ^^^^^^^^^^^^^^^^^^^^^^ File "<frozen codecs>", line 322, in decode UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb3 in position 33: invalid start byte ERROR: Unhandled python exception This is a Meson bug and should be reported! [end of output] note: This error originates from a subprocess, and is likely not a problem with pip. error: metadata-generation-failed × Encountered error while generating package metadata. ╰─> See above for output. note: This is an issue with the package mentioned above, not pip. hint: See above for details. 怎么解决

############################################################################### # Define strain values variable strain index 0.05 -0.05 0 variable strain_steps equal 500 # Loop for deformation processes label loop_strain # Starting parameters units metal atom_style atomic dimension 3 boundary p p p ############################################################################### # START DO NOT TOUCH read_data input.pos include masses.txt pair_style mlip mlip.ini pair_coeff * * # END DO NOT TOUCH ############################################################################### # Temperature and simulation parameters variable tempdamp equal 0.1 # Temperature damping variable pdamp equal 1 # Pressure damping variable mytimestep equal 0.001 # Timestep size variable dumprate equal 100 # Dump rate for output # Set up initial temperatures and steps variable temp_1 equal 100 # Initial temperature variable temp_2 equal 300 # Mid temperature variable temp_3 equal 500 variable temp_4 equal 700 variable temp_5 equal 900 # Final temperature variable steps_ramp_1 equal 1000 # Steps to ramp to mid temperature variable steps_hold_1 equal 1000 # Steps to hold at mid temperature variable steps_ramp_2 equal 1000 # Steps to ramp to final temperature variable steps_hold_2 equal 1000 # Steps to hold at final temperature # Computed values by LAMMPS compute temp all temp # Overall temperature compute pressure all pressure thermo_temp # Overall pressure compute pe all pe # Potential energy compute ke all ke # Kinetic en

zip
基于开源大模型的教学实训智能体软件,帮助教师生成课前备课设计、课后检测问答,提升效率与效果,提供学生全时在线练习与指导,实现教学相长。 智能教学辅助系统 这是一个智能教学辅助系统的前端项目,基于 Vue3+TypeScript 开发,使用 Ant Design Vue 作为 UI 组件库。 功能模块 用户模块 登录/注册功能,支持学生和教师角色 毛玻璃效果的登录界面 教师模块 备课与设计:根据课程大纲自动设计教学内容 考核内容生成:自动生成多样化考核题目及参考答案 学情数据分析:自动化检测学生答案,提供数据分析 学生模块 在线学习助手:结合教学内容解答问题 实时练习评测助手:生成随练题目并纠错 管理模块 用户管理:管理员/教师/学生等用户基本管理 课件资源管理:按学科列表管理教师备课资源 大屏概览:使用统计、效率指数、学习效果等 技术栈 Vue3 TypeScript Pinia 状态管理 Ant Design Vue 组件库 Axios 请求库 ByteMD 编辑器 ECharts 图表库 Monaco 编辑器 双主题支持(专业科技风/暗黑风) 开发指南 # 安装依赖 npm install # 启动开发服务器 npm run dev # 构建生产版本 npm run build 简介 本项目旨在开发一个基于开源大模型的教学实训智能体软件,帮助教师生成课前备课设计、课后检测问答,提升效率与效果,提供学生全时在线练习与指导,实现教学相长。

大家在看

recommend-type

高频双调谐谐振放大电路设计3MHz+电压200倍放大.zip

高频双调谐谐振放大电路设计3MHz+电压200倍放大.zip
recommend-type

只输入固定-vc实现windows多显示器编程的方法

P0.0 只输入固定 P0.1 P0CON.1 P0.2 P0CON.2 PORT_SET.PORT_REFEN P0.3 P0CON.3 自动“偷”从C2的交易应用程序在. PORT_SET.PORT_CLKEN PORT_SET.PORT_CLKOUT[0] P0.4 P0CON.4 C2调试的LED驱动器的时钟输入,如果作为 未启用. P0.5 PORT_CTRL.PORT_LED[1:0] 输出港口被迫为.阅读 实际LED驱动器的状态(开/关) 用户应阅读 RBIT_DATA.GPIO_LED_DRIVE 14只脚 不能用于在开发系统中,由于C2交易扰 乱输出. 参考区间的时钟频率 对抗 控制控制 评论评论 NVM的编程电压 VPP = 6.5 V 矩阵,和ROFF工业* PORT_CTRL 2 GPIO 1 矩阵,和ROFF工业* PORT_CTRL 3 参考 clk_ref GPIO 矩阵 4 C2DAT 产量 CLK_OUT GPIO 5 C2CLK LED驱动器 1 2 工业* PORT_CTRL 1 2 3 1 2 6 产量 CLK_OUT GPIO 1 2 1 1 1 PORT_SET.PORT_CLKEN PORT_SET.PORT_CLKOUT[1] P0.6 P0CON.6 P0.7 P0CON.7 P1.0 P1CON.0 P1.1 P1CON.1 7 8 9 GPIO GPIO GPIO 14只脚 14只脚 14只脚 *注:工业注:工业 代表“独立报”设置. “ 矩阵矩阵 and Roff 模式控制模拟垫电路. 116 修订版修订版1.0
recommend-type

半导体Semi ALD Tungsten W and TiN for Advanced Contact Application

ALD Tungsten, W and TiN for Advanced Contact Application
recommend-type

声纹识别数据集 IDMT-ISA-ELECTRIC-ENGINE

包含发动机正常、高负荷、损坏三种状态.wav声音片段,每种状态包含几百个片段,每个片段时长3S,可用于声纹类型识别,包含数据集介绍文档。
recommend-type

StepInt3-Plugin-x64:StepInt3插件(x64)-x64dbg的插件

StepInt3插件(x64)-x64dbg的插件 有关此插件的x86版本,请访问 概述 一个插件来解决int3断点异常 特征 自动跳过int3断点异常 从插件菜单启用/禁用的选项 如何安装 如果当前正在运行x64dbg(x64dbg 64位),请停止并退出。 将StepInt3.dp64复制到x64dbg\x64\plugins文件夹中。 启动x64dbg 信息 由撰写 使用 RadASM项目(.rap)用于管理和编译插件。 RadASM IDE可以在下载 该插件的x64版本使用 要构建此x64版本,还需要。 x64dbg x64dbg github x64dbg开关

最新推荐

recommend-type

使用Nginx实现负载均衡配置详解.doc

使用Nginx实现负载均衡配置详解.doc
recommend-type

无线电能传输LCC-S拓扑的MATLAB Simulink仿真:滑模控制与PI控制效能对比 无线电能传输

基于LCC-S拓扑的无线电能传输系统的MATLAB Simulink仿真模型,重点比较了滑模控制和PI控制方法的效果。文中首先描述了系统的硬件构成,包括两电平H桥逆变器、LCC补偿网络以及不可控整流桥。接着,作者通过MATLAB RF Toolbox确定了LCC参数组合,确保系统能在85kHz附近形成低阻抗通路。对于滑模控制,作者展示了具体的S函数实现及其关键参数设定,如滞环宽度的选择。而对于PI控制,则使用了Simulink自带的PID模块并进行了参数优化。最终,通过对负载阶跃响应、开关噪声频谱等方面的对比,得出滑模控制在响应速度上有显著优势,但也存在高频毛刺的问题。此外,还讨论了H桥死区时间设置对系统性能的影响。 适合人群:从事电力电子、无线充电技术研发的工程师和技术研究人员。 使用场景及目标:适用于需要评估不同控制方法在无线电能传输系统中表现的研究项目,帮助选择最优控制策略以提高系统性能。 其他说明:附带的文章有助于深入理解仿真模型的设计思路和具体实现细节。
recommend-type

基于开源大模型的教学实训智能体软件,基于 Vue3+TypeScript 开发,帮助教师生成课前备课设计、课后检测问答,提升效率与效果,提供学生全时在线练习与指导,实现教学相长 .zip

基于开源大模型的教学实训智能体软件,帮助教师生成课前备课设计、课后检测问答,提升效率与效果,提供学生全时在线练习与指导,实现教学相长。 智能教学辅助系统 这是一个智能教学辅助系统的前端项目,基于 Vue3+TypeScript 开发,使用 Ant Design Vue 作为 UI 组件库。 功能模块 用户模块 登录/注册功能,支持学生和教师角色 毛玻璃效果的登录界面 教师模块 备课与设计:根据课程大纲自动设计教学内容 考核内容生成:自动生成多样化考核题目及参考答案 学情数据分析:自动化检测学生答案,提供数据分析 学生模块 在线学习助手:结合教学内容解答问题 实时练习评测助手:生成随练题目并纠错 管理模块 用户管理:管理员/教师/学生等用户基本管理 课件资源管理:按学科列表管理教师备课资源 大屏概览:使用统计、效率指数、学习效果等 技术栈 Vue3 TypeScript Pinia 状态管理 Ant Design Vue 组件库 Axios 请求库 ByteMD 编辑器 ECharts 图表库 Monaco 编辑器 双主题支持(专业科技风/暗黑风) 开发指南 # 安装依赖 npm install # 启动开发服务器 npm run dev # 构建生产版本 npm run build 简介 本项目旨在开发一个基于开源大模型的教学实训智能体软件,帮助教师生成课前备课设计、课后检测问答,提升效率与效果,提供学生全时在线练习与指导,实现教学相长。
recommend-type

医疗病历交互系统源码-基于Web的医疗病历交互系统设计与实现-医疗病历交互网站代码-医疗病历交互项目代码

医疗病历交互-医疗病历交互系统-医疗病历交互系统源码-医疗病历交互系统代码-springboot医疗病历交互系统源码-基于springboot的医疗病历交互系统设计与实现-医疗病历交互管理系统代码
recommend-type

基于主从博弈理论的共享储能与电热综合需求响应优化运行研究

内容概要:本文探讨了在共享储能背景下,微网运营商与用户聚合商之间的主从博弈模型,旨在优化电热综合需求响应和电网技术的应用。文中详细介绍了主从博弈理论的基本概念及其在共享储能中的具体应用,证明了Stackelberg均衡解的存在性和唯一性。此外,作者还在MATLAB平台上进行了仿真实验,采用Yalmip工具和CPLEX求解器结合启发式算法对模型进行了优化,验证了理论的有效性。 适合人群:从事电力系统、能源管理及相关领域的研究人员、工程师以及高校相关专业的师生。 使用场景及目标:适用于希望深入了解共享储能在微网环境中优化运行机制的研究者,帮助他们掌握主从博弈理论的实际应用方法,提升对复杂能源系统的理解和应对能力。 其他说明:本文不仅提供了理论支持,还展示了具体的实验步骤和结果,有助于推动该领域的进一步发展。
recommend-type

Mockingbird v2:PocketMine-MP新防作弊机制详解

标题和描述中所涉及的知识点如下: 1. Mockingbird反作弊系统: Mockingbird是一个正在开发中的反作弊系统,专门针对PocketMine-MP服务器。PocketMine-MP是Minecraft Pocket Edition(Minecraft PE)的一个服务器软件,允许玩家在移动平台上共同游戏。随着游戏的普及,作弊问题也随之而来,因此Mockingbird的出现正是为了应对这种情况。 2. Mockingbird的版本迭代: 从描述中提到的“Mockingbird的v1变体”和“v2版本”的变化来看,Mockingbird正在经历持续的开发和改进过程。软件版本迭代是常见的开发实践,有助于修复已知问题,改善性能和用户体验,添加新功能等。 3. 服务器性能要求: 描述中强调了运行Mockingbird的服务器需要具备一定的性能,例如提及“WitherHosting的$ 1.25计划”,这暗示了反作弊系统对服务器资源的需求较高。这可能是因为反作弊机制需要频繁处理大量的数据和事件,以便及时检测和阻止作弊行为。 4. Waterdog问题: Waterdog是另一种Minecraft服务器软件,特别适合 PocketMine-MP。描述中提到如果将Mockingbird和Waterdog结合使用可能会遇到问题,这可能是因为两者在某些机制上的不兼容或Mockingbird对Waterdog的特定实现尚未完全优化。 5. GitHub使用及问题反馈: 作者鼓励用户通过GitHub问题跟踪系统来报告问题、旁路和功能建议。这是一个公共代码托管平台,广泛用于开源项目协作,便于开发者和用户进行沟通和问题管理。作者还提到请用户在GitHub上发布问题而不是在评论区留下不好的评论,这体现了良好的社区维护和用户交流的实践。 6. 软件标签: “pocketmine”和“anticheat”(反作弊)作为标签,说明Mockingbird是一个特别为PocketMine-MP平台开发的反作弊软件。而“PHP”则可能指的是Mockingbird的开发语言,虽然这个信息与常见的Java或C++等开发Minecraft相关软件的语言不同,但并不排除使用PHP进行服务器端开发的可能性,尤其是对于处理动态网页、服务器端脚本等场景。 7. 压缩包文件: “Mockingbird-stable”是一个文件名称,很可能表示这是一个包含最新稳定版Mockingbird反作弊系统的压缩包。通常,这样的文件名中包含“stable”意味着这是一个经过充分测试且推荐用于生产环境的版本。 8. 社区协作和用户参与: 特别感谢部分提到了shur,这可能是对某位贡献者或社区成员的感激之情。这种感谢表明了软件开发不仅是开发者个人的劳动成果,同时也依赖于社区的支持和参与,包括提供反馈、报告问题、贡献代码和文档等。 总结以上内容,我们可以看到Mockingbird作为一款反作弊系统,其开发和维护需要依赖于社区的广泛参与和支持,同时还需要强大的服务器后端作为支撑。通过不断迭代更新版本,开发者希望解决现有问题,提高反作弊效率,并希望与社区保持良好的沟通,以持续优化产品。
recommend-type

“历史人物独白解说”视频:数据处理的6种革命性技术

# 1. 数据处理的历史回顾与技术演进 数据处理的历史,从最初的简单机械记录,到如今复杂的数据处理体系,不仅反映了技术的演进,也映射了人类社会进步的轨迹。在计算机技术发展初期,数据处理主要依靠打孔卡片和磁带,通过批处理的方式进行,效率低下且出错率高。随着技术的发展,上世纪70年代,关系型数据库的出现大大提高了数据处理的效率和准确性,为数据处理技术的发展奠定了基础。 随后,随着互
recommend-type

怎么判断多级运放电路的稳定性?

<think>首先,用户的问题是关于判断多级运算放大器电路的稳定性。我需要根据系统级指令来构建回答。 系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考引用: - 引用[1]:关于集成运算放大电路的设计、组成和性能评估。 - 引用[2]:高频电路中运放的带宽限制,一级放大电路的增益通常为100倍,过高会引起振
recommend-type

利用AHP和节点集中度解决影响力最大化问题的Flask应用教程

从给定的文件信息中,我们可以提取以下相关知识点进行详细说明: ### 标题知识点 **IM问题与AHP结合** IM问题(Influence Maximization)是网络分析中的一个核心问题,旨在识别影响网络中信息传播的关键节点。为了求解IM问题,研究者们常常结合使用不同的算法和策略,其中AHP(Analytic Hierarchy Process,分析层次结构过程)作为一种决策分析方法,被用于评估网络节点的重要性。AHP通过建立层次模型,对各个因素进行比较排序,从而量化影响度,并通过一致性检验保证决策结果的有效性。将AHP应用于IM问题,意味着将分析网络节点影响的多个维度,比如节点的中心性(centrality)和影响力。 **集中度措施** 集中度(Centralization)是衡量网络节点分布状况的指标,它反映了网络中节点之间的连接关系。在网络分析中,集中度常用于识别网络中的“枢纽”或“中心”节点。例如,通过计算网络的度中心度(degree centrality)可以了解节点与其他节点的直接连接数量;接近中心度(closeness centrality)衡量节点到网络中其他所有节点的平均距离;中介中心度(betweenness centrality)衡量节点在连接网络中其他节点对的最短路径上的出现频率。集中度高意味着节点在网络中处于重要位置,对信息的流动和控制具有较大影响力。 ### 描述知识点 **Flask框架** Flask是一个轻量级的Web应用框架,它使用Python编程语言开发。它非常适合快速开发小型Web应用,以及作为微服务架构的一部分。Flask的一个核心特点是“微”,意味着它提供了基本的Web开发功能,同时保持了框架的小巧和灵活。Flask内置了开发服务器,支持Werkzeug WSGI工具包和Jinja2模板引擎,提供了RESTful请求分发和请求钩子等功能。 **应用布局** 一个典型的Flask应用会包含以下几个关键部分: - `app/`:这是应用的核心目录,包含了路由设置、视图函数、模型和控制器等代码文件。 - `static/`:存放静态文件,比如CSS样式表、JavaScript文件和图片等,这些文件的内容不会改变。 - `templates/`:存放HTML模板文件,Flask将使用这些模板渲染最终的HTML页面。模板语言通常是Jinja2。 - `wsgi.py`:WSGI(Web Server Gateway Interface)是Python应用程序和Web服务器之间的一种标准接口。这个文件通常用于部署到生产服务器时,作为应用的入口点。 **部署到Heroku** Heroku是一个支持多种编程语言的云平台即服务(PaaS),它允许开发者轻松部署、运行和管理应用。部署Flask应用到Heroku,需要几个步骤:首先,创建一个Procfile文件,告知Heroku如何启动应用;其次,确保应用的依赖关系被正确管理,通常通过一个requirements.txt文件列出所有依赖;最后,使用Git将应用推送到Heroku提供的仓库,Heroku会自动识别Procfile并开始部署过程。 ### 标签知识点 **HTML** HTML(HyperText Markup Language,超文本标记语言)是用于创建网页和Web应用的标准标记语言。它定义了网页的结构和内容。HTML文件由一系列元素组成,这些元素通过标签(tags)来表示,如`<p>`代表段落,`<a>`代表链接,`<img>`代表图像等。HTML5是当前使用的最新版本,支持更多的特性,如离线存储、多媒体和图形等。 ### 压缩包子文件的文件名称列表知识点 **IMproblem-using-AHP-and-centralisation-of-nodes-master** 这里的文件名称“IMproblem-using-AHP-and-centralisation-of-nodes-master”表明了一个GitHub仓库的名称,其中包含了源代码以及与项目相关的所有文件。从名称中可以看出,该仓库是关于如何结合AHP和节点集中度分析来解决IM问题的Flask应用程序。文件名中的“master”表明这是仓库的主分支(现在叫做main分支),它是项目最新的、可部署的代码版本。 综合来看,本段信息为我们提供了构建和部署一个使用Flask框架、针对IM问题使用AHP和节点集中度分析的Web应用的方法和步骤。同时,介绍了应用在不同环节中所需技术和组件的详细知识点。
recommend-type

视频内容自动生成算法:突破性的8大最新进展

# 1. 视频内容自动生成算法概述 ## 算法发展背景 随着人工智能技术的迅速发展,视频内容自动生成算法已经成为媒体和娱乐行业的重要工具。这些算法能够自动编辑和合成视频内容,使内容创作者能够以较低的成本和时间生成高质量的视频。从社交媒体动态到在线教育内容,视频内容自动生成的应用场景正在不断扩大。 ## 核心技术简述 视