#1044 : 状态压缩·一

本文介绍了一种使用状态压缩动态规划(状压DP)解决特定清扫任务的方法,旨在帮助乘务员在不打扰乘客的前提下尽可能多地清理垃圾。通过定义状态和转移方程,实现了在限定条件下最大化清扫效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB
描述

小Hi和小Ho在兑换到了喜欢的奖品之后,便继续起了他们的美国之行,思来想去,他们决定乘坐火车前往下一座城市——那座城市即将举行美食节!

但是不幸的是,小Hi和小Ho并没有能够买到很好的火车票——他们只能够乘坐最为破旧的火车进行他们的旅程。

不仅如此,因为美食节的吸引,许多人纷纷踏上了和小Hi小Ho一样的旅程,于是有相当多的人遭遇到了和小Hi小Ho一样的情况——这导致这辆车上的人非常非常的多,以至于都没有足够的位置能让每一个人都有地方坐下来。

小Hi和小Ho本着礼让他们的心情——当然还因为本来他们买的就是站票,老老实实的呆在两节车厢的结合处。他们本以为就能够这样安稳抵达目的地,但事与愿违,他们这节车厢的乘务员是一个强迫症,每隔一小会总是要清扫一次卫生,而时值深夜,大家都早已入睡,这种行为总是会惊醒一些人。而一旦相邻的一些乘客被惊醒了大多数的话,就会同乘务员吵起来,弄得大家都睡不好。

将这一切看在眼里的小Hi与小Ho决定利用他们的算法知识,来帮助这个有着强迫症的乘务员——在不与乘客吵起来的前提下尽可能多的清扫垃圾。

小Hi和小Ho所处的车厢可以被抽象成连成一列的N个位置,按顺序分别编号为1..N,每个位置上都有且仅有一名乘客在休息。同时每个位置上都有一些垃圾需要被清理,其中第i个位置的垃圾数量为Wi。乘务员可以选择其中一些位置进行清理,但是值得注意的是,一旦有编号连续的M个位置中有超过Q个的位置都在这一次清理中被选中的话(即这M个位置上的乘客有至少Q+1个被惊醒了),就会发生令人不愉快的口角。而小Hi和小Ho的任务是,计算选择哪些位置进行清理,在不发生口角的情况下,清扫尽可能多的垃圾。

提示一:无论是什么动态规划,都需要一个状态转移方程!

提示二:好像什么不对劲?状态压缩哪里去了?

输入

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第一行为三个正整数N、M和Q,意义如前文所述。

每组测试数据的第二行为N个整数,分别为W1到WN,代表每一个位置上的垃圾数目。

对于100%的数据,满足N<=1000, 2<=M<=10,1<=Q<=M, Wi<=100

输出

对于每组测试数据,输出一个整数Ans,表示在不发生口角的情况下,乘务员最多可以清扫的垃圾数目。

样例输入
5 2 1
36 9 80 69 85 
样例输出
201

解题思路:这道题就是一道典型的状压dp的题,关键是状态转移方程不太好想。dp[2][j]的意思是0,1,表示是不是当前的轮数。j是状态数。

dp[i&1][j]=max(dp[~i&1][j>>1],dp[~i&1][(j>>1)+num[M-1]])+(j&1)*w[i];

这个num[M-1]的作用是补位,看之前的M位数是否比当前的大,如果小的话就不管了,如果大的话就替换掉。

具体代码如下:

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int N,M,Q;
int dp[2][1<<11];
int w[1<<11];
int num[11];
int cn[1<<11];
void init()
{
    num[0]=1;
    for(int i=1;i<=10;i++)
    {
        num[i]=1<<i;
    }
    cn[0]=0,cn[1]=1,cn[2]=1;
    for(int i=3;i<=(1<<10);i++)
    {
        cn[i]=cn[i>>1]+cn[i&1];
    }
}
int main()
{
   // int N,M,Q;
    init();
    int lala;
    while(scanf("%d%d%d",&N,&M,&Q)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        lala=1<<M;
       // int flag=0;
        for(int i=1;i<=N;i++)
        {
            cin>>w[i];
        }
        for(int i=1;i<=N;i++)
        {
            for(int j=0;j<lala;j++)
            {
                if(cn[j]<=Q)
                dp[i&1][j]=max(dp[~i&1][j>>1],dp[~i&1][(j>>1)+num[M-1]])+(j&1)*w[i];//第二个如果超过Q那么肯定是0。所以不影响
               
            }
            
        }
        int ans=0;
        for(int i=1;i<lala;i++)
        {
            ans=max(ans,dp[N&1][i]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

#下面程序运行地报错: 分析过程中发生错误 启动分析线程失败: &#39;MultiModalApp&#39; object has no attribute &#39;update_progress&#39; 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: &#39;MultiModalApp&#39; object has no attribute &#39;update_progress&#39; —————————————————————————————————————————————————————————————————————————————— 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(&#39;.png&#39;): img.save(temp_path, optimize=True, quality=85, format=&#39;PNG&#39;) 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, &#39;analysis_thread&#39;) 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, &#39;analysis_thread&#39;) 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""" <div style=&#39;color:#64ffda; font-size:14pt; font-weight:bold; margin-bottom:10px;&#39;> 图片分析结果: {image_name} </div> <div style=&#39;color:#ccd6f6; font-size:12pt; line-height:1.6;&#39;>{formatted_result}</div> <div style=&#39;margin-top: 20px; color: #8892b0; font-size: 10pt; border-top: 1px solid #233554; padding-top: 10px;&#39;> 模型: <span style=&#39;color: #64ffda;&#39;>{model_name}</span> | 时间: <span style=&#39;color: #64ffda;&#39;>{timestamp}</span> </div> """ 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"<p style=&#39;text-indent: 2em;&#39;>{formatted_para}</p>") 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[&#39;image&#39;]} - {item[&#39;time&#39;]}") 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"预设 &#39;{preset_name}&#39; 已保存") def delete_preset(self): """删除预设""" preset_name = self.preset_combo.currentText() if preset_name in ["默认预设", "详细描述", "创意写作", "技术分析"]: QMessageBox.warning(self, "警告", "系统预设不能被删除") return reply = QMessageBox.question( self, "确认删除", f"确定要删除预设 &#39;{preset_name}&#39; 吗?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: index = self.preset_combo.currentIndex() self.preset_combo.removeItem(index) self.status_bar.showMessage(f"预设 &#39;{preset_name}&#39; 已删除") def stop_analysis(self): """停止分析""" if hasattr(self, &#39;analysis_thread&#39;) 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""" <div style=&#39;color:#ff6b6b; font-size:14pt; font-weight:bold; margin-bottom:10px;&#39;> 分析过程中发生错误 </div> <div style=&#39;color:#ccd6f6; font-size:12pt; line-height:1.6;&#39;> {error_msg.replace(&#39;\n&#39;, &#39;<br>&#39;)} </div> """ 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, &#39;analysis_thread&#39;) and self.analysis_thread.isRunning(): self.analysis_thread.stop() if hasattr(self, &#39;model_loader_thread&#39;) 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_())
最新发布
08-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值