629 lines
23 KiB
Python
629 lines
23 KiB
Python
"""
|
|
Lemontropia Suite - Vision Calibration Dialog
|
|
Wizard for calibrating Game Vision AI to user's game setup.
|
|
"""
|
|
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Optional, List, Dict, Any
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QWizard, QWizardPage, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
|
QPushButton, QComboBox, QCheckBox, QProgressBar, QGroupBox,
|
|
QFormLayout, QTextEdit, QMessageBox, QFileDialog, QListWidget,
|
|
QListWidgetItem, QSpinBox, QDoubleSpinBox, QWidget
|
|
)
|
|
from PyQt6.QtCore import Qt, QSettings, QThread, pyqtSignal
|
|
from PyQt6.QtGui import QFont, QPixmap, QImage
|
|
import numpy as np
|
|
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class CalibrationWorker(QThread):
|
|
"""Background worker for calibration processing."""
|
|
|
|
progress = pyqtSignal(int, str) # percentage, message
|
|
calibration_complete = pyqtSignal(dict)
|
|
error_occurred = pyqtSignal(str)
|
|
|
|
def __init__(self, screenshot_paths: List[Path], settings: Dict[str, Any]):
|
|
super().__init__()
|
|
self.screenshot_paths = screenshot_paths
|
|
self.settings = settings
|
|
self._cancelled = False
|
|
|
|
def run(self):
|
|
try:
|
|
from modules.game_vision_ai import GameVisionAI
|
|
|
|
self.progress.emit(0, "Initializing Game Vision AI...")
|
|
|
|
vision = GameVisionAI(
|
|
use_gpu=self.settings.get('use_gpu', True),
|
|
ocr_lang=self.settings.get('ocr_lang', 'en')
|
|
)
|
|
|
|
results = {
|
|
'screenshots_processed': 0,
|
|
'text_regions_detected': 0,
|
|
'icons_detected': 0,
|
|
'processing_times': [],
|
|
'errors': [],
|
|
'detected_regions': {},
|
|
'sample_extractions': []
|
|
}
|
|
|
|
total = len(self.screenshot_paths)
|
|
|
|
for i, screenshot_path in enumerate(self.screenshot_paths):
|
|
if self._cancelled:
|
|
self.error_occurred.emit("Calibration cancelled")
|
|
return
|
|
|
|
progress = int((i / total) * 100)
|
|
self.progress.emit(progress, f"Processing {screenshot_path.name}...")
|
|
|
|
try:
|
|
start_time = time.time()
|
|
result = vision.process_screenshot(
|
|
screenshot_path,
|
|
extract_text=self.settings.get('extract_text', True),
|
|
extract_icons=self.settings.get('extract_icons', True)
|
|
)
|
|
processing_time = (time.time() - start_time) * 1000
|
|
|
|
results['screenshots_processed'] += 1
|
|
results['text_regions_detected'] += len(result.text_regions)
|
|
results['icons_detected'] += len(result.icon_regions)
|
|
results['processing_times'].append(processing_time)
|
|
|
|
# Store sample extractions
|
|
if i < 3: # Store first 3 as samples
|
|
sample = {
|
|
'screenshot': str(screenshot_path),
|
|
'text_count': len(result.text_regions),
|
|
'icon_count': len(result.icon_regions),
|
|
'processing_time_ms': result.processing_time_ms,
|
|
'text_samples': [
|
|
{'text': t.text, 'confidence': t.confidence}
|
|
for t in result.text_regions[:5] # First 5 texts
|
|
]
|
|
}
|
|
results['sample_extractions'].append(sample)
|
|
|
|
except Exception as e:
|
|
results['errors'].append(f"{screenshot_path.name}: {str(e)}")
|
|
logger.error(f"Failed to process {screenshot_path}: {e}")
|
|
|
|
# Calculate statistics
|
|
if results['processing_times']:
|
|
results['avg_processing_time'] = np.mean(results['processing_times'])
|
|
results['min_processing_time'] = np.min(results['processing_times'])
|
|
results['max_processing_time'] = np.max(results['processing_times'])
|
|
|
|
self.progress.emit(100, "Calibration complete!")
|
|
self.calibration_complete.emit(results)
|
|
|
|
except Exception as e:
|
|
self.error_occurred.emit(str(e))
|
|
|
|
def cancel(self):
|
|
self._cancelled = True
|
|
|
|
|
|
class WelcomePage(QWizardPage):
|
|
"""Welcome page of calibration wizard."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Vision Calibration Wizard")
|
|
self.setSubTitle("Calibrate Game Vision AI for your game setup")
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
welcome_label = QLabel(
|
|
"<h2>Welcome to Vision Calibration</h2>"
|
|
"<p>This wizard will help you calibrate the Game Vision AI system "
|
|
"for optimal performance with your Entropia Universe setup.</p>"
|
|
"<p>You will need:</p>"
|
|
"<ul>"
|
|
"<li>A few sample screenshots from the game</li>"
|
|
"<li>Screenshots should include: loot windows, inventory, chat</li>"
|
|
"<li>About 2-5 minutes to complete</li>"
|
|
"</ul>"
|
|
)
|
|
welcome_label.setWordWrap(True)
|
|
layout.addWidget(welcome_label)
|
|
|
|
# Info box
|
|
info_group = QGroupBox("What will be calibrated?")
|
|
info_layout = QVBoxLayout(info_group)
|
|
|
|
info_text = QLabel(
|
|
"<ul>"
|
|
"<li><b>OCR Accuracy:</b> Text detection confidence and parameters</li>"
|
|
"<li><b>Icon Detection:</b> Loot window and item icon recognition</li>"
|
|
"<li><b>Performance:</b> Processing time optimization</li>"
|
|
"<li><b>GPU Setup:</b> Verify GPU acceleration is working</li>"
|
|
"</ul>"
|
|
)
|
|
info_text.setWordWrap(True)
|
|
info_layout.addWidget(info_text)
|
|
|
|
layout.addWidget(info_group)
|
|
layout.addStretch()
|
|
|
|
|
|
class ScreenshotSelectionPage(QWizardPage):
|
|
"""Page for selecting sample screenshots."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Select Sample Screenshots")
|
|
self.setSubTitle("Choose screenshots from your game for calibration")
|
|
self.screenshot_paths: List[Path] = []
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Instructions
|
|
instructions = QLabel(
|
|
"Select 3-10 screenshots that represent typical game situations:\n"
|
|
"• Loot windows with items\n"
|
|
"• Inventory screens\n"
|
|
"• Chat windows with text\n"
|
|
"• HUD with gear equipped"
|
|
)
|
|
instructions.setWordWrap(True)
|
|
layout.addWidget(instructions)
|
|
|
|
# File list
|
|
list_group = QGroupBox("Selected Screenshots")
|
|
list_layout = QVBoxLayout(list_group)
|
|
|
|
self.file_list = QListWidget()
|
|
list_layout.addWidget(self.file_list)
|
|
|
|
# Buttons
|
|
btn_layout = QHBoxLayout()
|
|
|
|
self.add_btn = QPushButton("Add Screenshots...")
|
|
self.add_btn.clicked.connect(self.add_screenshots)
|
|
btn_layout.addWidget(self.add_btn)
|
|
|
|
self.add_dir_btn = QPushButton("Add Directory...")
|
|
self.add_dir_btn.clicked.connect(self.add_directory)
|
|
btn_layout.addWidget(self.add_dir_btn)
|
|
|
|
self.remove_btn = QPushButton("Remove Selected")
|
|
self.remove_btn.clicked.connect(self.remove_selected)
|
|
btn_layout.addWidget(self.remove_btn)
|
|
|
|
self.clear_btn = QPushButton("Clear All")
|
|
self.clear_btn.clicked.connect(self.clear_all)
|
|
btn_layout.addWidget(self.clear_btn)
|
|
|
|
btn_layout.addStretch()
|
|
list_layout.addLayout(btn_layout)
|
|
|
|
layout.addWidget(list_group)
|
|
|
|
# Status
|
|
self.status_label = QLabel("No screenshots selected")
|
|
layout.addWidget(self.status_label)
|
|
|
|
def add_screenshots(self):
|
|
"""Add individual screenshot files."""
|
|
files, _ = QFileDialog.getOpenFileNames(
|
|
self, "Select Screenshots",
|
|
str(Path.home()),
|
|
"Images (*.png *.jpg *.jpeg *.bmp)"
|
|
)
|
|
|
|
for file_path in files:
|
|
path = Path(file_path)
|
|
if path not in self.screenshot_paths:
|
|
self.screenshot_paths.append(path)
|
|
self.file_list.addItem(path.name)
|
|
|
|
self.update_status()
|
|
|
|
def add_directory(self):
|
|
"""Add all images from a directory."""
|
|
dir_path = QFileDialog.getExistingDirectory(
|
|
self, "Select Screenshot Directory",
|
|
str(Path.home())
|
|
)
|
|
|
|
if dir_path:
|
|
path = Path(dir_path)
|
|
for ext in ['*.png', '*.jpg', '*.jpeg', '*.bmp']:
|
|
for file_path in path.glob(ext):
|
|
if file_path not in self.screenshot_paths:
|
|
self.screenshot_paths.append(file_path)
|
|
self.file_list.addItem(file_path.name)
|
|
|
|
self.update_status()
|
|
|
|
def remove_selected(self):
|
|
"""Remove selected screenshots."""
|
|
selected = self.file_list.currentRow()
|
|
if selected >= 0:
|
|
self.file_list.takeItem(selected)
|
|
del self.screenshot_paths[selected]
|
|
self.update_status()
|
|
|
|
def clear_all(self):
|
|
"""Clear all screenshots."""
|
|
self.file_list.clear()
|
|
self.screenshot_paths.clear()
|
|
self.update_status()
|
|
|
|
def update_status(self):
|
|
"""Update status label."""
|
|
count = len(self.screenshot_paths)
|
|
if count == 0:
|
|
self.status_label.setText("No screenshots selected")
|
|
elif count < 3:
|
|
self.status_label.setText(f"⚠️ {count} screenshot(s) selected (recommend at least 3)")
|
|
else:
|
|
self.status_label.setText(f"✅ {count} screenshot(s) selected")
|
|
|
|
def validatePage(self) -> bool:
|
|
"""Validate page before proceeding."""
|
|
if len(self.screenshot_paths) < 1:
|
|
QMessageBox.warning(self, "No Screenshots",
|
|
"Please select at least one screenshot.")
|
|
return False
|
|
return True
|
|
|
|
def get_screenshot_paths(self) -> List[Path]:
|
|
"""Get selected screenshot paths."""
|
|
return self.screenshot_paths
|
|
|
|
|
|
class SettingsPage(QWizardPage):
|
|
"""Page for configuring calibration settings."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Calibration Settings")
|
|
self.setSubTitle("Configure vision processing options")
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# GPU Settings
|
|
gpu_group = QGroupBox("GPU Acceleration")
|
|
gpu_layout = QFormLayout(gpu_group)
|
|
|
|
self.use_gpu_cb = QCheckBox("Use GPU for processing")
|
|
self.use_gpu_cb.setChecked(True)
|
|
self.use_gpu_cb.setToolTip(
|
|
"Enable GPU acceleration for faster processing"
|
|
)
|
|
gpu_layout.addRow(self.use_gpu_cb)
|
|
|
|
self.gpu_info_label = QLabel("GPU info will be detected during calibration")
|
|
gpu_layout.addRow("GPU:", self.gpu_info_label)
|
|
|
|
layout.addWidget(gpu_group)
|
|
|
|
# OCR Settings
|
|
ocr_group = QGroupBox("OCR (Text Recognition)")
|
|
ocr_layout = QFormLayout(ocr_group)
|
|
|
|
self.extract_text_cb = QCheckBox("Enable text extraction")
|
|
self.extract_text_cb.setChecked(True)
|
|
ocr_layout.addRow(self.extract_text_cb)
|
|
|
|
self.ocr_lang_combo = QComboBox()
|
|
self.ocr_lang_combo.addItem("English", "en")
|
|
self.ocr_lang_combo.addItem("Swedish", "sv")
|
|
ocr_layout.addRow("Language:", self.ocr_lang_combo)
|
|
|
|
layout.addWidget(ocr_group)
|
|
|
|
# Icon Settings
|
|
icon_group = QGroupBox("Icon Detection")
|
|
icon_layout = QFormLayout(icon_group)
|
|
|
|
self.extract_icons_cb = QCheckBox("Enable icon extraction")
|
|
self.extract_icons_cb.setChecked(True)
|
|
icon_layout.addRow(self.extract_icons_cb)
|
|
|
|
self.icon_size_combo = QComboBox()
|
|
self.icon_size_combo.addItem("Small (32x32)", "small")
|
|
self.icon_size_combo.addItem("Medium (48x48)", "medium")
|
|
self.icon_size_combo.addItem("Large (64x64)", "large")
|
|
icon_layout.addRow("Icon Size:", self.icon_size_combo)
|
|
|
|
self.auto_detect_window_cb = QCheckBox("Auto-detect loot windows")
|
|
self.auto_detect_window_cb.setChecked(True)
|
|
icon_layout.addRow(self.auto_detect_window_cb)
|
|
|
|
layout.addWidget(icon_group)
|
|
layout.addStretch()
|
|
|
|
def get_settings(self) -> Dict[str, Any]:
|
|
"""Get calibration settings."""
|
|
return {
|
|
'use_gpu': self.use_gpu_cb.isChecked(),
|
|
'extract_text': self.extract_text_cb.isChecked(),
|
|
'extract_icons': self.extract_icons_cb.isChecked(),
|
|
'ocr_lang': self.ocr_lang_combo.currentData(),
|
|
'icon_size': self.icon_size_combo.currentData(),
|
|
'auto_detect_window': self.auto_detect_window_cb.isChecked()
|
|
}
|
|
|
|
|
|
class ProcessingPage(QWizardPage):
|
|
"""Page for running calibration processing."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Processing")
|
|
self.setSubTitle("Running calibration...")
|
|
self.is_complete = False
|
|
self.calibration_results: Optional[Dict] = None
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Progress
|
|
self.status_label = QLabel("Ready to start calibration")
|
|
layout.addWidget(self.status_label)
|
|
|
|
self.progress_bar = QProgressBar()
|
|
self.progress_bar.setRange(0, 100)
|
|
self.progress_bar.setValue(0)
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
# Results area
|
|
self.results_text = QTextEdit()
|
|
self.results_text.setReadOnly(True)
|
|
self.results_text.setPlaceholderText("Calibration results will appear here...")
|
|
layout.addWidget(self.results_text)
|
|
|
|
# Buttons
|
|
btn_layout = QHBoxLayout()
|
|
|
|
self.start_btn = QPushButton("Start Calibration")
|
|
self.start_btn.clicked.connect(self.start_calibration)
|
|
btn_layout.addWidget(self.start_btn)
|
|
|
|
self.cancel_btn = QPushButton("Cancel")
|
|
self.cancel_btn.clicked.connect(self.cancel_calibration)
|
|
self.cancel_btn.setEnabled(False)
|
|
btn_layout.addWidget(self.cancel_btn)
|
|
|
|
btn_layout.addStretch()
|
|
layout.addLayout(btn_layout)
|
|
|
|
def initializePage(self):
|
|
"""Called when page is shown."""
|
|
self.results_text.clear()
|
|
self.progress_bar.setValue(0)
|
|
self.status_label.setText("Ready to start calibration")
|
|
self.is_complete = False
|
|
self.start_btn.setEnabled(True)
|
|
|
|
def start_calibration(self):
|
|
"""Start calibration processing."""
|
|
wizard = self.wizard()
|
|
screenshot_page = wizard.page(1) # ScreenshotSelectionPage
|
|
settings_page = wizard.page(2) # SettingsPage
|
|
|
|
screenshot_paths = screenshot_page.get_screenshot_paths()
|
|
settings = settings_page.get_settings()
|
|
|
|
if not screenshot_paths:
|
|
QMessageBox.warning(self, "No Screenshots",
|
|
"No screenshots selected!")
|
|
return
|
|
|
|
self.start_btn.setEnabled(False)
|
|
self.cancel_btn.setEnabled(True)
|
|
self.status_label.setText("Starting calibration...")
|
|
|
|
# Start worker thread
|
|
self.worker = CalibrationWorker(screenshot_paths, settings)
|
|
self.worker.progress.connect(self.on_progress)
|
|
self.worker.calibration_complete.connect(self.on_complete)
|
|
self.worker.error_occurred.connect(self.on_error)
|
|
self.worker.start()
|
|
|
|
def on_progress(self, percentage: int, message: str):
|
|
"""Handle progress update."""
|
|
self.progress_bar.setValue(percentage)
|
|
self.status_label.setText(message)
|
|
self.results_text.append(message)
|
|
|
|
def on_complete(self, results: Dict):
|
|
"""Handle calibration completion."""
|
|
self.calibration_results = results
|
|
self.is_complete = True
|
|
self.cancel_btn.setEnabled(False)
|
|
|
|
# Display results
|
|
summary = f"""
|
|
<b>Calibration Complete!</b>
|
|
|
|
Screenshots processed: {results['screenshots_processed']}
|
|
Text regions detected: {results['text_regions_detected']}
|
|
Icons detected: {results['icons_detected']}
|
|
"""
|
|
if 'avg_processing_time' in results:
|
|
summary += f"Average processing time: {results['avg_processing_time']:.1f}ms\n"
|
|
|
|
if results.get('errors'):
|
|
summary += f"\nErrors: {len(results['errors'])}"
|
|
|
|
self.results_text.append(summary)
|
|
|
|
# Enable next button
|
|
self.completeChanged.emit()
|
|
|
|
def on_error(self, error: str):
|
|
"""Handle calibration error."""
|
|
self.status_label.setText(f"Error: {error}")
|
|
self.results_text.append(f"❌ Error: {error}")
|
|
self.start_btn.setEnabled(True)
|
|
self.cancel_btn.setEnabled(False)
|
|
|
|
def cancel_calibration(self):
|
|
"""Cancel calibration."""
|
|
if hasattr(self, 'worker'):
|
|
self.worker.cancel()
|
|
self.status_label.setText("Cancelling...")
|
|
|
|
def isComplete(self) -> bool:
|
|
return self.is_complete
|
|
|
|
def get_results(self) -> Optional[Dict]:
|
|
"""Get calibration results."""
|
|
return self.calibration_results
|
|
|
|
|
|
class ResultsPage(QWizardPage):
|
|
"""Final page showing calibration results."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Calibration Results")
|
|
self.setSubTitle("Review and save calibration results")
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
self.results_label = QLabel("Processing results will appear here...")
|
|
self.results_label.setWordWrap(True)
|
|
layout.addWidget(self.results_label)
|
|
|
|
# Recommendations
|
|
self.recommendations_label = QLabel("")
|
|
self.recommendations_label.setWordWrap(True)
|
|
layout.addWidget(self.recommendations_label)
|
|
|
|
layout.addStretch()
|
|
|
|
def initializePage(self):
|
|
"""Called when page is shown."""
|
|
wizard = self.wizard()
|
|
processing_page = wizard.page(3) # ProcessingPage
|
|
results = processing_page.get_results()
|
|
|
|
if results:
|
|
# Format results
|
|
text = f"""
|
|
<h3>Calibration Results</h3>
|
|
|
|
<p><b>Processing Summary:</b></p>
|
|
<ul>
|
|
<li>Screenshots processed: {results['screenshots_processed']}</li>
|
|
<li>Text regions detected: {results['text_regions_detected']}</li>
|
|
<li>Icons detected: {results['icons_detected']}</li>
|
|
</ul>
|
|
"""
|
|
if 'avg_processing_time' in results:
|
|
text += f"""
|
|
<p><b>Performance:</b></p>
|
|
<ul>
|
|
<li>Average processing time: {results['avg_processing_time']:.1f}ms</li>
|
|
<li>Min processing time: {results['min_processing_time']:.1f}ms</li>
|
|
<li>Max processing time: {results['max_processing_time']:.1f}ms</li>
|
|
</ul>
|
|
"""
|
|
self.results_label.setText(text)
|
|
|
|
# Generate recommendations
|
|
recommendations = self._generate_recommendations(results)
|
|
self.recommendations_label.setText(recommendations)
|
|
|
|
# Save results to settings
|
|
self._save_calibration_results(results)
|
|
|
|
def _generate_recommendations(self, results: Dict) -> str:
|
|
"""Generate calibration recommendations."""
|
|
recs = ["<h3>Recommendations</h3><ul>"]
|
|
|
|
# Performance recommendations
|
|
if 'avg_processing_time' in results:
|
|
avg_time = results['avg_processing_time']
|
|
if avg_time < 100:
|
|
recs.append("<li>✅ Excellent performance! GPU acceleration is working well.</li>")
|
|
elif avg_time < 500:
|
|
recs.append("<li>✅ Good performance. Processing is reasonably fast.</li>")
|
|
else:
|
|
recs.append("<li>⚠️ Processing is slow. Consider enabling GPU or reducing screenshot resolution.</li>")
|
|
|
|
# Detection recommendations
|
|
total_regions = results['text_regions_detected'] + results['icons_detected']
|
|
if total_regions == 0:
|
|
recs.append("<li>⚠️ No text or icons detected. Check screenshot quality and game UI visibility.</li>")
|
|
elif results['text_regions_detected'] == 0:
|
|
recs.append("<li>⚠️ No text detected. Try adjusting OCR thresholds or check image clarity.</li>")
|
|
elif results['icons_detected'] == 0:
|
|
recs.append("<li>⚠️ No icons detected. Ensure screenshots include loot windows.</li>")
|
|
else:
|
|
recs.append("<li>✅ Detection is working. Text and icons are being recognized.</li>")
|
|
|
|
recs.append("</ul>")
|
|
return "".join(recs)
|
|
|
|
def _save_calibration_results(self, results: Dict):
|
|
"""Save calibration results to settings."""
|
|
settings = QSettings("Lemontropia", "GameVision")
|
|
settings.setValue("calibration/last_run", time.time())
|
|
settings.setValue("calibration/screenshots_processed", results['screenshots_processed'])
|
|
settings.setValue("calibration/avg_processing_time", results.get('avg_processing_time', 0))
|
|
settings.setValue("calibration/text_detection_rate", results['text_regions_detected'])
|
|
settings.setValue("calibration/icon_detection_rate", results['icons_detected'])
|
|
settings.sync()
|
|
|
|
|
|
class VisionCalibrationWizard(QWizard):
|
|
"""
|
|
Wizard for calibrating Game Vision AI.
|
|
"""
|
|
|
|
calibration_complete = pyqtSignal(dict)
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle("Vision Calibration Wizard")
|
|
self.setMinimumSize(700, 550)
|
|
|
|
# Add pages
|
|
self.addPage(WelcomePage())
|
|
self.addPage(ScreenshotSelectionPage())
|
|
self.addPage(SettingsPage())
|
|
self.addPage(ProcessingPage())
|
|
self.addPage(ResultsPage())
|
|
|
|
self.setWizardStyle(QWizard.WizardStyle.ModernStyle)
|
|
|
|
def accept(self):
|
|
"""Handle wizard completion."""
|
|
processing_page = self.page(3)
|
|
results = processing_page.get_results()
|
|
|
|
if results:
|
|
self.calibration_complete.emit(results)
|
|
|
|
super().accept()
|
|
|
|
|
|
# Export
|
|
__all__ = ['VisionCalibrationWizard', 'CalibrationWorker']
|