fix: resolve 3 runtime errors in inference and UI

- result_handler: add _InferenceResultEncoder to handle dataclass objects
  (ObjectDetectionResult, ClassificationResult) in JSON serialization;
  fixes "Object of type ObjectDetectionResult is not JSON serializable"

- deployment: replace textCursor().movePosition() with toPlainText/setPlainText
  for log trimming; eliminates QTextCursor cross-thread Qt warning

- main: remove duplicate setAttribute(AA_EnableHighDpiScaling) call in
  setup_application() which ran after QApplication was already created;
  fixes "Attribute Qt::AA_EnableHighDpiScaling must be set before
  QCoreApplication is created"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
abin 2026-04-06 19:32:30 +08:00
parent d2fdbf85ee
commit 6e9885404c
3 changed files with 27 additions and 15 deletions

View File

@ -3,8 +3,18 @@ import json
import csv import csv
import os import os
import time import time
import dataclasses
from typing import Any, Dict, List from typing import Any, Dict, List
class _InferenceResultEncoder(json.JSONEncoder):
"""將 dataclass 推論結果物件轉為可序列化的 dict。"""
def default(self, o):
if dataclasses.is_dataclass(o) and not isinstance(o, type):
return dataclasses.asdict(o)
return super().default(o)
class ResultSerializer: class ResultSerializer:
""" """
Serializes inference results into various formats. Serializes inference results into various formats.
@ -12,8 +22,10 @@ class ResultSerializer:
def to_json(self, data: Dict[str, Any]) -> str: def to_json(self, data: Dict[str, Any]) -> str:
""" """
Serializes data to a JSON string. Serializes data to a JSON string.
Dataclass objects (ObjectDetectionResult, ClassificationResult, etc.)
are automatically converted to dicts via _InferenceResultEncoder.
""" """
return json.dumps(data, indent=2) return json.dumps(data, indent=2, cls=_InferenceResultEncoder)
def to_csv(self, data: List[Dict[str, Any]], fieldnames: List[str]) -> str: def to_csv(self, data: List[Dict[str, Any]], fieldnames: List[str]) -> str:
""" """

View File

@ -233,9 +233,9 @@ class SingleInstance:
def setup_application(): def setup_application():
"""Initialize and configure the QApplication.""" """Initialize and configure the QApplication."""
# Enable high DPI support BEFORE creating QApplication # High DPI attributes must be set before QApplication is created.
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) # They are set in main() before the first QApplication instantiation.
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) # Do NOT set them here — QApplication already exists at this point.
# Create QApplication if it doesn't exist # Create QApplication if it doesn't exist
if not QApplication.instance(): if not QApplication.instance():

View File

@ -1163,21 +1163,21 @@ Stage Configurations:
def update_terminal_output(self, terminal_text: str): def update_terminal_output(self, terminal_text: str):
"""Update the terminal output display with new text.""" """Update the terminal output display with new text."""
try: try:
# Use append() instead of setPlainText() for better performance and no truncation
self.terminal_output_display.append(terminal_text.rstrip('\n')) self.terminal_output_display.append(terminal_text.rstrip('\n'))
# Auto-scroll to bottom # Auto-scroll to bottom
scrollbar = self.terminal_output_display.verticalScrollBar() scrollbar = self.terminal_output_display.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum()) scrollbar.setValue(scrollbar.maximum())
# Optional: Limit total lines to prevent excessive memory usage # Limit total lines to prevent excessive memory usage.
# Only trim if we have way too many lines (e.g., > 1000) # Use toPlainText/setPlainText to avoid QTextCursor cross-thread warnings.
document = self.terminal_output_display.document() document = self.terminal_output_display.document()
if document.lineCount() > 1000: if document.lineCount() > 1000:
cursor = self.terminal_output_display.textCursor() lines = self.terminal_output_display.toPlainText().split('\n')
cursor.movePosition(cursor.Start) trimmed = '\n'.join(lines[-800:]) # Keep last 800 lines
cursor.movePosition(cursor.Down, cursor.KeepAnchor, 200) # Select first 200 lines self.terminal_output_display.setPlainText(trimmed)
cursor.removeSelectedText() # Restore scroll to bottom after setPlainText resets it
scrollbar.setValue(scrollbar.maximum())
except Exception as e: except Exception as e:
print(f"Error updating terminal output: {e}") print(f"Error updating terminal output: {e}")