改成雙層config架構,調整app處理config資料, 增加inno setup的iss file, auto renew global config
This commit is contained in:
parent
75e5925046
commit
a8ed1ac592
100
README.md
100
README.md
@ -1,26 +1,7 @@
|
|||||||
<!-- main page
|
# Kneron Academy v2.0
|
||||||
1. -> example (python code) -> 讀 folder 檔案 -> 選
|
這個應用程式是一個基於 Python、PyQt5、OpenCV 以及 Kneron SDK(kp)開發的 AI 應用 APP,使用者可以透過鏡頭、麥克風或上傳的方式經由 Kneron NPU 裝置進行實時運算。
|
||||||
-> 抓 camera -> -i image -m model
|
|
||||||
2. run code -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- config for python script 需要有以下的資料:
|
|
||||||
1. function name to display
|
|
||||||
2. script name
|
|
||||||
3. device availibility (considering if the script name doesn't consist dongle type)
|
|
||||||
4. model's name
|
|
||||||
5. input information (frames, images, voice etc.)
|
|
||||||
6. input parameters
|
|
||||||
7. output parameters -->
|
|
||||||
|
|
||||||
|
|
||||||
# Innovedus AI Playground
|
|
||||||
|
|
||||||
這個應用程式是一個 AI Playground,用於通過相機鏡頭或使用者上傳圖片進行推論(例如火災檢測)。應用程式基於 Python、PyQt5、OpenCV 以及 Kneron SDK(kp)開發,支援 Video 模式與 Image 模式。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 目錄
|
## 目錄
|
||||||
- [安裝設定](#安裝設定)
|
- [安裝設定](#安裝設定)
|
||||||
- [專案架構](#專案架構)
|
- [專案架構](#專案架構)
|
||||||
@ -74,8 +55,9 @@ project/
|
|||||||
upload/
|
upload/
|
||||||
└── photos, videos, or mp3 files
|
└── photos, videos, or mp3 files
|
||||||
utils/
|
utils/
|
||||||
└── plugins/
|
├── config.json
|
||||||
├── mode1/
|
├── REAMDE.md
|
||||||
|
│── mode1/
|
||||||
│ ├── model1/
|
│ ├── model1/
|
||||||
│ │ ├── script.py
|
│ │ ├── script.py
|
||||||
│ │ ├── model_file(s)
|
│ │ ├── model_file(s)
|
||||||
@ -93,19 +75,17 @@ utils/
|
|||||||
├── script.py
|
├── script.py
|
||||||
├── model_file(s)
|
├── model_file(s)
|
||||||
└── config.json
|
└── config.json
|
||||||
└──firmware\
|
firmware\
|
||||||
├── KLXXX/
|
├── KLXXX/
|
||||||
│ ├── fw_scpu.bin
|
│ ├── fw_scpu.bin
|
||||||
│ ├── fw_ncpu.bin
|
│ ├── fw_ncpu.bin
|
||||||
│ ├── VERSION
|
│ ├── VERSION
|
||||||
│ └── other files
|
│ └── other files
|
||||||
├── KLXXX/
|
└── KLXXX/
|
||||||
├── fw_scpu.bin
|
├── fw_scpu.bin
|
||||||
├── fw_ncpu.bin
|
├── fw_ncpu.bin
|
||||||
├── VERSION
|
├── VERSION
|
||||||
└── other files
|
└── other files
|
||||||
└──config.json
|
|
||||||
└──REAMDE.md
|
|
||||||
```
|
```
|
||||||
## 功能概述
|
## 功能概述
|
||||||
|
|
||||||
@ -164,3 +144,69 @@ pyinstaller --onefile --windowed main.py --additional-hooks-dir=hooks --add-data
|
|||||||
|
|
||||||
## APP資料加密
|
## APP資料加密
|
||||||
目前預計使用 [pyarmor](https://github.com/dashingsoft/pyarmor) 進行加密
|
目前預計使用 [pyarmor](https://github.com/dashingsoft/pyarmor) 進行加密
|
||||||
|
|
||||||
|
|
||||||
|
## Script & Model 的設定
|
||||||
|
整個 utils folder 會分成兩層: global config 和 model config
|
||||||
|
|
||||||
|
model config 範例如下
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"display_name": "人臉偵測 (ResNet-18)",
|
||||||
|
"description": "使用ResNet-18架構的高精度人臉偵測模型,可即時標記影像中的人臉位置",
|
||||||
|
"model_file": "face_detection.nef",
|
||||||
|
"input_info": {
|
||||||
|
"type": "video",
|
||||||
|
"supported_formats": ["mp4", "avi", "webm"]
|
||||||
|
},
|
||||||
|
"input_parameters": {
|
||||||
|
"threshold": 0.75,
|
||||||
|
"max_faces": 10,
|
||||||
|
"tracking": true
|
||||||
|
},
|
||||||
|
"compatible_devices": ["KL520", "KL720"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
global config 範例如下
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"mode": "face_recognition",
|
||||||
|
"display_name": "人臉辨識",
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"name": "face_detection",
|
||||||
|
"display_name": "人臉偵測 (ResNet-18)",
|
||||||
|
"description": "基於ResNet-18的高精度人臉偵測",
|
||||||
|
"compatible_devices": ["KL520", "KL720"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "age_gender",
|
||||||
|
"display_name": "年齡性別辨識 (VGG-Face)",
|
||||||
|
"description": "使用VGG-Face架構辨識人臉年齡與性別",
|
||||||
|
"compatible_devices": ["KL520", "KL720"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "object_detection",
|
||||||
|
"display_name": "物體偵測",
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"name": "yolo_v5",
|
||||||
|
"display_name": "物體偵測 (YOLOv5)",
|
||||||
|
"description": "使用YOLOv5進行實時物體偵測與分類",
|
||||||
|
"compatible_devices": ["KL720"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rcnn",
|
||||||
|
"display_name": "精確物體識別 (Faster R-CNN)",
|
||||||
|
"description": "以Faster R-CNN為基礎的高精度物體識別",
|
||||||
|
"compatible_devices": ["KL720"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
27
dist/test.iss
vendored
27
dist/test.iss
vendored
@ -1,12 +1,10 @@
|
|||||||
; Script generated by the Inno Setup Script Wizard.
|
; Script generated by the Inno Setup Script Wizard.
|
||||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
#define MyAppName "Kneron Academy"
|
#define MyAppName "Kneron Academy"
|
||||||
#define MyAppVersion "2.0"
|
#define MyAppVersion "2.0"
|
||||||
#define MyAppPublisher "Innovedus Inc."
|
#define MyAppPublisher "Innovedus Inc."
|
||||||
#define MyAppURL "https://www.example.com/"
|
#define MyAppURL "https://www.example.com/"
|
||||||
#define MyAppExeName "main.exe"
|
#define MyAppExeName "main.exe"
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
; 唯一的 AppId,請勿在其他應用程式中重複使用
|
; 唯一的 AppId,請勿在其他應用程式中重複使用
|
||||||
AppId={{0894596D-D78B-4D8C-97CC-D90FE98E26E0}}
|
AppId={{0894596D-D78B-4D8C-97CC-D90FE98E26E0}}
|
||||||
@ -28,37 +26,34 @@ PrivilegesRequired=lowest
|
|||||||
OutputBaseFilename=mysetup
|
OutputBaseFilename=mysetup
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
WizardStyle=modern
|
WizardStyle=modern
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
[Tasks]
|
[Tasks]
|
||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
; 定義配對元件,讓使用者選擇是否安裝預設的 Script 與 Model
|
; 定義配對元件,讓使用者選擇是否安裝預設的 Script 與 Model
|
||||||
[Components]
|
[Components]
|
||||||
Name: "pair1"; Description: "Fire Detection";
|
Name: "pair1"; Description: "Fire Detection";
|
||||||
Name: "pair2"; Description: "Photo_quality";
|
Name: "pair2"; Description: "Photo_quality";
|
||||||
|
Name: "pair3"; Description: "Test Mode";
|
||||||
[Files]
|
[Files]
|
||||||
; 安裝主要執行檔到 {app} 目錄
|
; 安裝主要執行檔到 {app} 目錄
|
||||||
Source: "C:\Users\mason\Code\demo_gui\dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "C:\Users\mason\Code\Kneron Academy 2.0\dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
; 安裝 config.json 檔案 (不需使用者勾選)
|
||||||
|
Source: "C:\Users\mason\AppData\Local\Kneron_Academy\utils\config.json"; DestDir: "{localappdata}\Kneron_Academy\utils"; Flags: ignoreversion
|
||||||
; --- 配對1 ---
|
; --- 配對1 ---
|
||||||
; pair 1
|
; pair 1
|
||||||
Source: "C:\Users\mason\Downloads\Kneron_Academy\utils\models\fire_detection_520.nef"; DestDir: "{localappdata}\Kneron_Academy\utils\models\fire_detection_520.nef"; Components: pair1; Flags: ignoreversion recursesubdirs
|
Source: "C:\Users\mason\AppData\Local\Kneron_Academy\utils\fire detection\yuan\*"; DestDir: "{localappdata}\Kneron_Academy\utils\fire detection\yuan"; Components: pair1; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
Source: "C:\Users\mason\Downloads\Kneron_Academy\utils\scripts\fire_detection_520.py"; DestDir: "{localappdata}\Kneron_Academy\utils\scripts\fire_detection_520.py"; Components: pair1; Flags: ignoreversion recursesubdirs
|
|
||||||
; pair 2
|
; pair 2
|
||||||
Source: "C:\Users\mason\Downloads\Kneron_Academy\utils\models\photo_scorer_520.nef"; DestDir: "{localappdata}\Kneron_Academy\utils\models\photo_scorer_520.nef"; Components: pair2; Flags: ignoreversion recursesubdirs
|
Source: "C:\Users\mason\AppData\Local\Kneron_Academy\utils\photo quality\ruby\*"; DestDir: "{localappdata}\Kneron_Academy\utils\photo quality\ruby"; Components: pair2; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
Source: "C:\Users\mason\Downloads\Kneron_Academy\utils\scripts\photo_quality_520.py"; DestDir: "{localappdata}\Kneron_Academy\utils\scripts\photo_quality_520.py"; Components: pair2; Flags: ignoreversion recursesubdirs
|
; pair 3
|
||||||
|
Source: "C:\Users\mason\AppData\Local\Kneron_Academy\utils\test mode\test\*"; DestDir: "{localappdata}\Kneron_Academy\utils\test mode\test"; Components: pair3; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
[Dirs]
|
[Dirs]
|
||||||
; 如有需要隱藏這些資料夾,設定隱藏屬性
|
; 如有需要隱藏這些資料夾,設定隱藏屬性
|
||||||
Name: "{localappdata}\Kneron_Academy\utils\scripts"; Attribs: hidden
|
Name: "{localappdata}\Kneron_Academy\utils\fire detection\yuan"; Attribs: hidden
|
||||||
Name: "{localappdata}\Kneron_Academy\utils\models"; Attribs: hidden
|
Name: "{localappdata}\Kneron_Academy\utils\photo quality\ruby"; Attribs: hidden
|
||||||
|
Name: "{localappdata}\Kneron_Academy\utils\test mode\test"; Attribs: hidden
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||||
@ -4,12 +4,11 @@ APPDATA_PATH = os.environ.get("LOCALAPPDATA")
|
|||||||
# 取得專案根目錄的絕對路徑並設定 UXUI_ASSETS 為絕對路徑
|
# 取得專案根目錄的絕對路徑並設定 UXUI_ASSETS 為絕對路徑
|
||||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
UXUI_ASSETS = os.path.join(PROJECT_ROOT, "uxui", "")
|
UXUI_ASSETS = os.path.join(PROJECT_ROOT, "uxui", "")
|
||||||
MODEL = os.path.join(APPDATA_PATH,"Kneron_Academy", "utils", "models", "")
|
# 新版路徑結構 (不需要獨立的 models 和 scripts 資料夾)
|
||||||
SCRIPT = os.path.join(APPDATA_PATH, "Kneron_Academy", "utils", "scripts", "")
|
UTILS_DIR = os.path.join(APPDATA_PATH, "Kneron_Academy", "utils")
|
||||||
SCRIPT_CONFIG = os.path.join(APPDATA_PATH, "Kneron_Academy", "utils", "configs.json")
|
SCRIPT_CONFIG = os.path.join(UTILS_DIR, "config.json")
|
||||||
UPLOAD_DIR = os.path.join(APPDATA_PATH, "Kneron_Academy", "uploads")
|
UPLOAD_DIR = os.path.join(APPDATA_PATH, "Kneron_Academy", "uploads")
|
||||||
FW_DIR = os.path.join(APPDATA_PATH, "Kneron_Academy", "utils", "firmware")
|
FW_DIR = os.path.join(APPDATA_PATH, "Kneron_Academy", "firmware")
|
||||||
|
|
||||||
# Global Constants
|
# Global Constants
|
||||||
APP_NAME = "Innovedus AI Playground"
|
APP_NAME = "Innovedus AI Playground"
|
||||||
WINDOW_SIZE = (1200, 900)
|
WINDOW_SIZE = (1200, 900)
|
||||||
|
|||||||
@ -8,8 +8,8 @@ from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal
|
|||||||
from PyQt5.QtGui import QPixmap, QMovie, QImage
|
from PyQt5.QtGui import QPixmap, QMovie, QImage
|
||||||
|
|
||||||
from ..config import (UXUI_ASSETS, WINDOW_SIZE, BACKGROUND_COLOR, SECONDARY_COLOR,
|
from ..config import (UXUI_ASSETS, WINDOW_SIZE, BACKGROUND_COLOR, SECONDARY_COLOR,
|
||||||
BUTTON_STYLE, MASK_STYLE, PROJECT_ROOT, SCRIPT_CONFIG, SCRIPT, UPLOAD_DIR,
|
BUTTON_STYLE, MASK_STYLE, PROJECT_ROOT, SCRIPT_CONFIG, UTILS_DIR , UPLOAD_DIR,
|
||||||
FW_DIR, DongleModelMap, DongleIconMap, MODEL)
|
FW_DIR, DongleModelMap, DongleIconMap)
|
||||||
|
|
||||||
from ..services.device_service import check_available_device
|
from ..services.device_service import check_available_device
|
||||||
|
|
||||||
@ -56,8 +56,9 @@ def qimage_to_numpy(qimage):
|
|||||||
|
|
||||||
#────────────────────────────────────────────────────────────
|
#────────────────────────────────────────────────────────────
|
||||||
# 動態載入 inference 模組的函式
|
# 動態載入 inference 模組的函式
|
||||||
def load_inference_module(script_path):
|
def load_inference_module(mode, model_name):
|
||||||
module_name = os.path.splitext(os.path.basename(script_path))[0]
|
script_path = os.path.join(UTILS_DIR, mode, model_name, "script.py")
|
||||||
|
module_name = f"{mode}_{model_name}"
|
||||||
spec = importlib.util.spec_from_file_location(module_name, script_path)
|
spec = importlib.util.spec_from_file_location(module_name, script_path)
|
||||||
module = importlib.util.module_from_spec(spec)
|
module = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
@ -70,25 +71,29 @@ class InferenceWorkerThread(QThread):
|
|||||||
# 傳出 inference 結果,型態可依需求調整(例如 dict 或 tuple)
|
# 傳出 inference 結果,型態可依需求調整(例如 dict 或 tuple)
|
||||||
inference_result_signal = pyqtSignal(object)
|
inference_result_signal = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, frame_queue, inference_script_path, min_interval=0.5, mse_threshold=500, once_mode=False):
|
def __init__(self, frame_queue, mode, model_name, min_interval=0.5, mse_threshold=500, once_mode=False):
|
||||||
"""
|
"""
|
||||||
frame_queue: 傳入的 frame 佇列(numpy 陣列)
|
frame_queue: 傳入的 frame 佇列(numpy 陣列)
|
||||||
inference_script_path: inference 模組的檔案路徑
|
mode: 模式名稱 (如 'face_recognition')
|
||||||
|
model_name: 模型名稱 (如 'face_detection')
|
||||||
min_interval: 最小 inference 間隔 (秒)
|
min_interval: 最小 inference 間隔 (秒)
|
||||||
mse_threshold: 當前後 frame 之均方誤差低於此值則視為相似
|
mse_threshold: 當前後 frame 之均方誤差低於此值則視為相似
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.frame_queue = frame_queue
|
self.frame_queue = frame_queue
|
||||||
self.inference_script_path = inference_script_path
|
self.mode = mode
|
||||||
|
self.model_name = model_name
|
||||||
self.min_interval = min_interval
|
self.min_interval = min_interval
|
||||||
self.mse_threshold = mse_threshold
|
self.mse_threshold = mse_threshold
|
||||||
self._running = True
|
self._running = True
|
||||||
self.once_mode = once_mode # 新增旗標:如果 True,則只做一次推論
|
self.once_mode = once_mode
|
||||||
self.last_inference_time = 0
|
self.last_inference_time = 0
|
||||||
self.last_frame = None
|
self.last_frame = None
|
||||||
self.cached_result = None
|
self.cached_result = None
|
||||||
# 動態載入 inference 模組(假設介面函式為 inference(frame, params))
|
|
||||||
self.inference_module = load_inference_module(self.inference_script_path)
|
# 動態載入 inference 模組
|
||||||
|
script_path = os.path.join(UTILS_DIR, mode, model_name, "script.py")
|
||||||
|
self.inference_module = load_inference_module(mode, model_name)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while self._running:
|
while self._running:
|
||||||
@ -149,12 +154,13 @@ class MainWindow(QWidget):
|
|||||||
self.video_writer = None
|
self.video_writer = None
|
||||||
self.recorded_frames = []
|
self.recorded_frames = []
|
||||||
self.destination = None
|
self.destination = None
|
||||||
# 目前選用的 tool 配置,初始為 None
|
|
||||||
self.current_tool_config = None
|
self.current_tool_config = None
|
||||||
self.inference_worker = None
|
self.inference_worker = None
|
||||||
# 建立 frame 佇列,限制最大數量
|
|
||||||
self.inference_queue = queue.Queue(maxsize=10)
|
self.inference_queue = queue.Queue(maxsize=10)
|
||||||
|
|
||||||
|
# 確保目錄存在並更新配置
|
||||||
|
self.generate_global_config()
|
||||||
|
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
|
|
||||||
def init_ui(self): # 初始化UI (暫時不需要修改)
|
def init_ui(self): # 初始化UI (暫時不需要修改)
|
||||||
@ -479,28 +485,59 @@ class MainWindow(QWidget):
|
|||||||
def select_tool(self, tool_config):
|
def select_tool(self, tool_config):
|
||||||
print("選擇工具:", tool_config.get("display_name"))
|
print("選擇工具:", tool_config.get("display_name"))
|
||||||
self.current_tool_config = tool_config
|
self.current_tool_config = tool_config
|
||||||
new_script_path = os.path.join(SCRIPT, tool_config.get("script"))
|
|
||||||
|
|
||||||
# 取得工具的 input type
|
# 獲取模式和模型名稱
|
||||||
|
mode = tool_config.get("mode", "")
|
||||||
|
model_name = tool_config.get("model_name", "")
|
||||||
|
|
||||||
|
# 構建模型路徑
|
||||||
|
model_path = os.path.join(UTILS_DIR, mode, model_name)
|
||||||
|
|
||||||
|
# 讀取特定模型的詳細配置
|
||||||
|
model_config_path = os.path.join(model_path, "config.json")
|
||||||
|
detailed_config = {}
|
||||||
|
|
||||||
|
if os.path.exists(model_config_path):
|
||||||
|
try:
|
||||||
|
with open(model_config_path, "r", encoding="utf-8") as f:
|
||||||
|
detailed_config = json.load(f)
|
||||||
|
# 合併基本配置和詳細配置
|
||||||
|
tool_config = {**tool_config, **detailed_config}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading model config: {e}")
|
||||||
|
|
||||||
|
# 獲取工具的輸入類型
|
||||||
input_info = tool_config.get("input_info", {})
|
input_info = tool_config.get("input_info", {})
|
||||||
tool_type = input_info.get("type", "video")
|
tool_type = input_info.get("type", "video")
|
||||||
print("type:", tool_type)
|
print("type:", tool_type)
|
||||||
once_mode = True if tool_type == "image" else False
|
once_mode = True if tool_type == "image" else False
|
||||||
|
|
||||||
# 組合 input_params(從 tool_config 中預設值)
|
# 組合input_params
|
||||||
input_params = tool_config.get("input_parameters", {}).copy()
|
input_params = tool_config.get("input_parameters", {}).copy()
|
||||||
|
|
||||||
|
# 處理設備相關設定
|
||||||
if hasattr(self, "selected_device") and self.selected_device:
|
if hasattr(self, "selected_device") and self.selected_device:
|
||||||
input_params["usb_port_id"] = self.selected_device.get("usb_port_id", 0)
|
input_params["usb_port_id"] = self.selected_device.get("usb_port_id", 0)
|
||||||
# 直接使用設備的 model(在 parse_and_store_devices 已填入)
|
|
||||||
dongle = self.selected_device.get("dongle", "unknown")
|
dongle = self.selected_device.get("dongle", "unknown")
|
||||||
print("選取的 dongle:", dongle)
|
print("選取的 dongle:", dongle)
|
||||||
# 利用 model 當作子資料夾名稱組合 firmware 路徑
|
|
||||||
|
# 檢查模型是否支援該設備
|
||||||
|
compatible_devices = tool_config.get("compatible_devices", [])
|
||||||
|
if compatible_devices and dongle not in compatible_devices:
|
||||||
|
self.show_custom_message(
|
||||||
|
QMessageBox.Warning,
|
||||||
|
"設備不兼容",
|
||||||
|
f"所選模型不支援 {dongle} 設備。\n支援的設備: {', '.join(compatible_devices)}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 處理韌體路徑
|
||||||
scpu_path = os.path.join(FW_DIR, dongle, "fw_scpu.bin")
|
scpu_path = os.path.join(FW_DIR, dongle, "fw_scpu.bin")
|
||||||
ncpu_path = os.path.join(FW_DIR, dongle, "fw_ncpu.bin")
|
ncpu_path = os.path.join(FW_DIR, dongle, "fw_ncpu.bin")
|
||||||
input_params["scpu_path"] = scpu_path
|
input_params["scpu_path"] = scpu_path
|
||||||
input_params["ncpu_path"] = ncpu_path
|
input_params["ncpu_path"] = ncpu_path
|
||||||
else:
|
else:
|
||||||
|
# 預設設備處理邏輯不變
|
||||||
if self.connected_devices and len(self.connected_devices) > 0:
|
if self.connected_devices and len(self.connected_devices) > 0:
|
||||||
input_params["usb_port_id"] = self.connected_devices[0].get("usb_port_id", 0)
|
input_params["usb_port_id"] = self.connected_devices[0].get("usb_port_id", 0)
|
||||||
print("Warning: 沒有特別選取 dongle, 預設使用第一個設備")
|
print("Warning: 沒有特別選取 dongle, 預設使用第一個設備")
|
||||||
@ -508,11 +545,11 @@ class MainWindow(QWidget):
|
|||||||
input_params["usb_port_id"] = 0
|
input_params["usb_port_id"] = 0
|
||||||
print("Warning: 沒有連接設備, 使用預設 usb_port_id 0")
|
print("Warning: 沒有連接設備, 使用預設 usb_port_id 0")
|
||||||
|
|
||||||
# 若工具模式需要檔案輸入,則處理 file_path
|
# 處理檔案輸入
|
||||||
if tool_type in ["image", "voice"]:
|
if tool_type in ["image", "voice"]:
|
||||||
|
# 處理邏輯不變
|
||||||
if hasattr(self, "destination") and self.destination:
|
if hasattr(self, "destination") and self.destination:
|
||||||
input_params["file_path"] = self.destination
|
input_params["file_path"] = self.destination
|
||||||
# 讀取上傳的圖片並推入 inference_queue
|
|
||||||
uploaded_img = cv2.imread(self.destination)
|
uploaded_img = cv2.imread(self.destination)
|
||||||
if uploaded_img is not None:
|
if uploaded_img is not None:
|
||||||
if not self.inference_queue.full():
|
if not self.inference_queue.full():
|
||||||
@ -526,12 +563,11 @@ class MainWindow(QWidget):
|
|||||||
input_params["file_path"] = ""
|
input_params["file_path"] = ""
|
||||||
print("Warning: 需要檔案輸入,但尚未上傳檔案。")
|
print("Warning: 需要檔案輸入,但尚未上傳檔案。")
|
||||||
|
|
||||||
# 從 config 讀取 model_info,組合 model 路徑
|
# 添加模型檔案路徑
|
||||||
if "model_info" in tool_config:
|
if "model_file" in tool_config:
|
||||||
model_name = tool_config["model_info"].get("name", "")
|
model_file = tool_config["model_file"]
|
||||||
# 假設模型檔案存放在 "src\\utils\\models" 資料夾下,根據需要調整路徑
|
model_file_path = os.path.join(model_path, model_file)
|
||||||
model_path = os.path.join(MODEL, model_name)
|
input_params["model"] = model_file_path
|
||||||
input_params["model"] = model_path
|
|
||||||
|
|
||||||
print("input_params:", input_params)
|
print("input_params:", input_params)
|
||||||
|
|
||||||
@ -540,10 +576,11 @@ class MainWindow(QWidget):
|
|||||||
self.inference_worker.stop()
|
self.inference_worker.stop()
|
||||||
self.inference_worker = None
|
self.inference_worker = None
|
||||||
|
|
||||||
# 建立新的 inference worker
|
# 建立新的 inference worker (使用修改後的參數)
|
||||||
self.inference_worker = InferenceWorkerThread(
|
self.inference_worker = InferenceWorkerThread(
|
||||||
self.inference_queue,
|
self.inference_queue,
|
||||||
new_script_path,
|
mode,
|
||||||
|
model_name,
|
||||||
min_interval=0.5,
|
min_interval=0.5,
|
||||||
mse_threshold=500,
|
mse_threshold=500,
|
||||||
once_mode=once_mode
|
once_mode=once_mode
|
||||||
@ -551,7 +588,7 @@ class MainWindow(QWidget):
|
|||||||
self.inference_worker.input_params = input_params
|
self.inference_worker.input_params = input_params
|
||||||
self.inference_worker.inference_result_signal.connect(self.handle_inference_result)
|
self.inference_worker.inference_result_signal.connect(self.handle_inference_result)
|
||||||
self.inference_worker.start()
|
self.inference_worker.start()
|
||||||
print(f"Inference worker 已切換到模組:{new_script_path}")
|
print(f"Inference worker 已切換到模組:{mode}/{model_name}")
|
||||||
|
|
||||||
if tool_type == "video":
|
if tool_type == "video":
|
||||||
self.start_camera()
|
self.start_camera()
|
||||||
@ -566,20 +603,21 @@ class MainWindow(QWidget):
|
|||||||
if os.path.exists(SCRIPT_CONFIG):
|
if os.path.exists(SCRIPT_CONFIG):
|
||||||
with open(SCRIPT_CONFIG, "r", encoding="utf-8") as f:
|
with open(SCRIPT_CONFIG, "r", encoding="utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
tools = config.get("tools", [])
|
plugins = config.get("plugins", [])
|
||||||
# print("tools: ", tools)
|
|
||||||
else:
|
else:
|
||||||
print("找不到 toolbox config 檔案,使用空的工具列表")
|
# 若無配置檔,則嘗試自動生成
|
||||||
tools = []
|
plugins = self.generate_global_config().get("plugins", [])
|
||||||
|
if not plugins:
|
||||||
|
print("無法生成配置,使用空的工具列表")
|
||||||
|
|
||||||
# 建立工具箱介面
|
# 創建工具箱UI
|
||||||
toolbox_frame = QFrame(self)
|
toolbox_frame = QFrame(self)
|
||||||
toolbox_frame.setStyleSheet(f"border: none; background: {SECONDARY_COLOR}; border-radius: 15px;")
|
toolbox_frame.setStyleSheet(f"border: none; background: {SECONDARY_COLOR}; border-radius: 15px;")
|
||||||
toolbox_frame.setFixedHeight(450)
|
toolbox_frame.setFixedHeight(450)
|
||||||
toolbox_frame.setFixedWidth(240)
|
toolbox_frame.setFixedWidth(240)
|
||||||
toolbox_layout = QVBoxLayout(toolbox_frame)
|
toolbox_layout = QVBoxLayout(toolbox_frame)
|
||||||
|
|
||||||
# 建立標題列
|
# 標題列
|
||||||
title_layout = QHBoxLayout()
|
title_layout = QHBoxLayout()
|
||||||
title_container = QWidget()
|
title_container = QWidget()
|
||||||
container_layout = QHBoxLayout(title_container)
|
container_layout = QHBoxLayout(title_container)
|
||||||
@ -596,12 +634,33 @@ class MainWindow(QWidget):
|
|||||||
title_layout.addWidget(title_container)
|
title_layout.addWidget(title_container)
|
||||||
toolbox_layout.addLayout(title_layout)
|
toolbox_layout.addLayout(title_layout)
|
||||||
|
|
||||||
# 根據 JSON 配置建立工具按鈕
|
# 建立工具按鈕 (分類顯示)
|
||||||
for tool in tools:
|
for plugin in plugins:
|
||||||
name = tool.get("display_name", "Unnamed Tool")
|
mode = plugin.get("mode", "")
|
||||||
button = QPushButton(name)
|
display_name = plugin.get("display_name", "")
|
||||||
# 使用 lambda 捕捉 tool 設定,避免 late binding 問題
|
|
||||||
button.clicked.connect(lambda checked, t=tool: self.select_tool(t))
|
# 添加分類標題
|
||||||
|
category_label = QLabel(display_name)
|
||||||
|
category_label.setStyleSheet("color: white; font-size: 16px; font-weight: bold; margin-top: 10px;")
|
||||||
|
toolbox_layout.addWidget(category_label)
|
||||||
|
|
||||||
|
# 添加該分類下的所有模型按鈕
|
||||||
|
for model in plugin.get("models", []):
|
||||||
|
model_name = model.get("name", "")
|
||||||
|
display_name = model.get("display_name", "")
|
||||||
|
|
||||||
|
# 建立工具配置
|
||||||
|
tool_config = {
|
||||||
|
"mode": mode,
|
||||||
|
"model_name": model_name,
|
||||||
|
"display_name": display_name,
|
||||||
|
"description": model.get("description", ""),
|
||||||
|
"compatible_devices": model.get("compatible_devices", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
# 建立按鈕
|
||||||
|
button = QPushButton(display_name)
|
||||||
|
button.clicked.connect(lambda checked, t=tool_config: self.select_tool(t))
|
||||||
button.setStyleSheet(BUTTON_STYLE)
|
button.setStyleSheet(BUTTON_STYLE)
|
||||||
button.setFixedHeight(40)
|
button.setFixedHeight(40)
|
||||||
toolbox_layout.addWidget(button)
|
toolbox_layout.addWidget(button)
|
||||||
@ -1141,3 +1200,84 @@ class MainWindow(QWidget):
|
|||||||
self.overlay.hide()
|
self.overlay.hide()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in hide_device_popup: {e}")
|
print(f"Error in hide_device_popup: {e}")
|
||||||
|
|
||||||
|
def generate_global_config(self):
|
||||||
|
"""掃描目錄結構並生成全局配置檔案"""
|
||||||
|
try:
|
||||||
|
config = {"plugins": []}
|
||||||
|
|
||||||
|
# 確認 utils 目錄存在
|
||||||
|
if not os.path.exists(UTILS_DIR):
|
||||||
|
os.makedirs(UTILS_DIR, exist_ok=True)
|
||||||
|
print(f"已建立 UTILS_DIR: {UTILS_DIR}")
|
||||||
|
|
||||||
|
# 列出 utils 目錄下的所有項目以進行偵錯
|
||||||
|
print(f"UTILS_DIR 內容: {os.listdir(UTILS_DIR) if os.path.exists(UTILS_DIR) else '目錄不存在'}")
|
||||||
|
|
||||||
|
# 掃描模式目錄(第一層子目錄)
|
||||||
|
mode_dirs = [d for d in os.listdir(UTILS_DIR)
|
||||||
|
if os.path.isdir(os.path.join(UTILS_DIR, d)) and not d.startswith('_')]
|
||||||
|
|
||||||
|
print(f"找到的模式目錄: {mode_dirs}")
|
||||||
|
|
||||||
|
for mode_name in mode_dirs:
|
||||||
|
mode_path = os.path.join(UTILS_DIR, mode_name)
|
||||||
|
|
||||||
|
mode_info = {
|
||||||
|
"mode": mode_name,
|
||||||
|
"display_name": mode_name.replace("_", " ").title(),
|
||||||
|
"models": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 列出該模式目錄下的所有項目以進行偵錯
|
||||||
|
print(f"模式 {mode_name} 的內容: {os.listdir(mode_path)}")
|
||||||
|
|
||||||
|
# 掃描模型目錄(第二層子目錄)
|
||||||
|
model_dirs = [d for d in os.listdir(mode_path)
|
||||||
|
if os.path.isdir(os.path.join(mode_path, d)) and not d.startswith('_')]
|
||||||
|
|
||||||
|
print(f"在模式 {mode_name} 中找到的模型: {model_dirs}")
|
||||||
|
|
||||||
|
for model_name in model_dirs:
|
||||||
|
model_path = os.path.join(mode_path, model_name)
|
||||||
|
|
||||||
|
# 檢查模型配置檔案
|
||||||
|
model_config_path = os.path.join(model_path, "config.json")
|
||||||
|
if os.path.exists(model_config_path):
|
||||||
|
try:
|
||||||
|
with open(model_config_path, "r", encoding="utf-8") as f:
|
||||||
|
model_config = json.load(f)
|
||||||
|
|
||||||
|
print(f"已成功讀取模型配置: {model_config_path}")
|
||||||
|
|
||||||
|
model_summary = {
|
||||||
|
"name": model_name,
|
||||||
|
"display_name": model_config.get("display_name", model_name.replace("_", " ").title()),
|
||||||
|
"description": model_config.get("description", ""),
|
||||||
|
"compatible_devices": model_config.get("compatible_devices", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_info["models"].append(model_summary)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"讀取模型配置時發生錯誤 {model_config_path}: {e}")
|
||||||
|
else:
|
||||||
|
print(f"未找到模型配置檔: {model_config_path}")
|
||||||
|
# 可選:自動創建模板配置檔
|
||||||
|
self.create_model_config_template(model_path)
|
||||||
|
|
||||||
|
# 只添加含有模型的模式
|
||||||
|
if mode_info["models"]:
|
||||||
|
config["plugins"].append(mode_info)
|
||||||
|
|
||||||
|
# 寫入配置檔
|
||||||
|
os.makedirs(os.path.dirname(SCRIPT_CONFIG), exist_ok=True)
|
||||||
|
with open(SCRIPT_CONFIG, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"全局配置已生成: {SCRIPT_CONFIG}")
|
||||||
|
return config
|
||||||
|
except Exception as e:
|
||||||
|
print(f"生成全局配置時發生錯誤: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return {"plugins": []}
|
||||||
Loading…
x
Reference in New Issue
Block a user