calibrate-suite 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- calibrate_suite-0.1.0.dist-info/METADATA +761 -0
- calibrate_suite-0.1.0.dist-info/RECORD +47 -0
- calibrate_suite-0.1.0.dist-info/WHEEL +5 -0
- calibrate_suite-0.1.0.dist-info/entry_points.txt +3 -0
- calibrate_suite-0.1.0.dist-info/licenses/LICENSE +201 -0
- calibrate_suite-0.1.0.dist-info/top_level.txt +4 -0
- fleet_server/__init__.py +32 -0
- fleet_server/app.py +377 -0
- fleet_server/config.py +91 -0
- fleet_server/templates/error.html +57 -0
- fleet_server/templates/index.html +137 -0
- fleet_server/templates/viewer.html +490 -0
- fleet_server/utils.py +178 -0
- gui/__init__.py +2 -0
- gui/assets/2d-or-3d-fleet-upload.png +0 -0
- gui/assets/2d_3d_overlay_output.jpg +0 -0
- gui/assets/3d-or-2d-overlay_page.png +0 -0
- gui/assets/3d-or-2d-record-page.png +0 -0
- gui/assets/3d_3d_overlay_output.png +0 -0
- gui/assets/3d_or_2d_calibrate-page.png +0 -0
- gui/assets/GUI_homepage.png +0 -0
- gui/assets/hardware_setup.jpeg +0 -0
- gui/assets/single_lidar_calibrate_page.png +0 -0
- gui/assets/single_lidar_output.png +0 -0
- gui/assets/single_lidar_record_page.png +0 -0
- gui/assets/virya.jpg +0 -0
- gui/main.py +23 -0
- gui/widgets/calibrator_widget.py +977 -0
- gui/widgets/extractor_widget.py +561 -0
- gui/widgets/home_widget.py +117 -0
- gui/widgets/recorder_widget.py +127 -0
- gui/widgets/single_lidar_widget.py +673 -0
- gui/widgets/three_d_calib_widget.py +87 -0
- gui/widgets/two_d_calib_widget.py +86 -0
- gui/widgets/uploader_widget.py +151 -0
- gui/widgets/validator_widget.py +614 -0
- gui/windows/main_window.py +56 -0
- gui/windows/main_window_ui.py +65 -0
- rviz_configs/2D-3D.rviz +183 -0
- rviz_configs/3D-3D.rviz +184 -0
- rviz_configs/default_calib.rviz +167 -0
- utils/__init__.py +13 -0
- utils/calibration_common.py +23 -0
- utils/cli_calibrate.py +53 -0
- utils/cli_fleet_server.py +64 -0
- utils/data_extractor_common.py +87 -0
- utils/gui_helpers.py +25 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from PyQt5.QtWidgets import (QWidget, QTabWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
2
|
+
QPushButton, QGroupBox, QLineEdit, QComboBox, QTextEdit,
|
|
3
|
+
QRadioButton, QCheckBox, QFileDialog, QMessageBox, QSizePolicy, QScrollArea)
|
|
4
|
+
from PyQt5.QtCore import pyqtSignal, Qt, QProcess
|
|
5
|
+
import os
|
|
6
|
+
import glob
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# Add src/ to path for utils import
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
14
|
+
from utils import wrap_with_back
|
|
15
|
+
|
|
16
|
+
# Import existing widgets for other tabs
|
|
17
|
+
try:
|
|
18
|
+
from ..widgets.extractor_widget import ExtractorWidget
|
|
19
|
+
from ..widgets.calibrator_widget import ThreeDCalibratorWidget
|
|
20
|
+
from ..widgets.validator_widget import ThreeDValidatorWidget
|
|
21
|
+
from ..widgets.uploader_widget import UploaderWidget
|
|
22
|
+
except ImportError:
|
|
23
|
+
from gui.widgets.extractor_widget import ExtractorWidget
|
|
24
|
+
from gui.widgets.calibrator_widget import ThreeDCalibratorWidget
|
|
25
|
+
from gui.widgets.validator_widget import ThreeDValidatorWidget
|
|
26
|
+
from gui.widgets.uploader_widget import UploaderWidget
|
|
27
|
+
|
|
28
|
+
class ThreeDThreeDWidget(QWidget):
|
|
29
|
+
go_home = pyqtSignal()
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.initUI()
|
|
34
|
+
|
|
35
|
+
def initUI(self):
|
|
36
|
+
layout = QVBoxLayout()
|
|
37
|
+
|
|
38
|
+
# Header
|
|
39
|
+
header = QHBoxLayout()
|
|
40
|
+
btn_back = QPushButton("← Home")
|
|
41
|
+
btn_back.clicked.connect(self.go_home.emit)
|
|
42
|
+
btn_back.setFixedWidth(100)
|
|
43
|
+
btn_back.setStyleSheet("""
|
|
44
|
+
QPushButton { background-color: #2E8B57; border-radius: 8px; color: white; }
|
|
45
|
+
QPushButton:hover { background-color: #689F38; }
|
|
46
|
+
""")
|
|
47
|
+
header.addWidget(btn_back)
|
|
48
|
+
|
|
49
|
+
title = QLabel("3D-3D Calibration (LiDAR-Camera)")
|
|
50
|
+
title.setStyleSheet("font-size: 18px; font-weight: bold; color: #2196F3;")
|
|
51
|
+
header.addWidget(title)
|
|
52
|
+
header.addStretch()
|
|
53
|
+
layout.addLayout(header)
|
|
54
|
+
|
|
55
|
+
# Tabs
|
|
56
|
+
self.tabs = QTabWidget()
|
|
57
|
+
self.tabs.setStyleSheet("""
|
|
58
|
+
QTabBar::tab {
|
|
59
|
+
min-width: 150px;
|
|
60
|
+
padding: 8px;
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
}
|
|
63
|
+
QTabBar::tab:selected {
|
|
64
|
+
background-color: #1B5E20;
|
|
65
|
+
color: white;
|
|
66
|
+
}
|
|
67
|
+
""")
|
|
68
|
+
self.init_tabs()
|
|
69
|
+
layout.addWidget(self.tabs)
|
|
70
|
+
|
|
71
|
+
self.setLayout(layout)
|
|
72
|
+
|
|
73
|
+
def init_tabs(self):
|
|
74
|
+
# 1. Record (Multimodal) - Uses ExtractorWidget
|
|
75
|
+
# Note: ExtractorWidget now contains the Multimodal Recording logic
|
|
76
|
+
rec_widget = ExtractorWidget()
|
|
77
|
+
rec_widget.back_clicked.connect(self.go_home.emit)
|
|
78
|
+
self.tabs.addTab(rec_widget, "Record (Multimodal)")
|
|
79
|
+
|
|
80
|
+
# 2. Calibration (3D-3D specific)
|
|
81
|
+
self.tabs.addTab(wrap_with_back(ThreeDCalibratorWidget(), self.go_home), "Calibrate")
|
|
82
|
+
|
|
83
|
+
# 3. Validation (3D-3D specific - Point cloud overlay visualization)
|
|
84
|
+
self.tabs.addTab(wrap_with_back(ThreeDValidatorWidget(), self.go_home), "Validate")
|
|
85
|
+
|
|
86
|
+
# 4. Upload (Shared with 2D-3D)
|
|
87
|
+
self.tabs.addTab(wrap_with_back(UploaderWidget(), self.go_home), "Fleet Upload")
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from PyQt5.QtWidgets import (QWidget, QTabWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
2
|
+
QPushButton, QGroupBox, QLineEdit, QComboBox, QTextEdit,
|
|
3
|
+
QRadioButton, QCheckBox, QFileDialog, QMessageBox, QSizePolicy, QScrollArea)
|
|
4
|
+
from PyQt5.QtCore import pyqtSignal, Qt, QProcess
|
|
5
|
+
import os
|
|
6
|
+
import glob
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# Add src/ to path for utils import
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
14
|
+
from utils import wrap_with_back
|
|
15
|
+
|
|
16
|
+
# Import existing widgets for other tabs
|
|
17
|
+
try:
|
|
18
|
+
from ..widgets.extractor_widget import ExtractorWidget
|
|
19
|
+
from ..widgets.calibrator_widget import CalibratorWidget
|
|
20
|
+
from ..widgets.validator_widget import ValidatorWidget
|
|
21
|
+
from ..widgets.uploader_widget import UploaderWidget
|
|
22
|
+
except ImportError:
|
|
23
|
+
from gui.widgets.extractor_widget import ExtractorWidget
|
|
24
|
+
from gui.widgets.calibrator_widget import CalibratorWidget
|
|
25
|
+
from gui.widgets.validator_widget import ValidatorWidget
|
|
26
|
+
from gui.widgets.uploader_widget import UploaderWidget
|
|
27
|
+
|
|
28
|
+
class TwoDThreeDWidget(QWidget):
|
|
29
|
+
go_home = pyqtSignal()
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.initUI()
|
|
34
|
+
|
|
35
|
+
def initUI(self):
|
|
36
|
+
layout = QVBoxLayout()
|
|
37
|
+
|
|
38
|
+
# Header
|
|
39
|
+
header = QHBoxLayout()
|
|
40
|
+
btn_back = QPushButton("← Home")
|
|
41
|
+
btn_back.clicked.connect(self.go_home.emit)
|
|
42
|
+
btn_back.setFixedWidth(100)
|
|
43
|
+
btn_back.setStyleSheet("""
|
|
44
|
+
QPushButton { background-color: #2E8B57; border-radius: 8px; color: white; }
|
|
45
|
+
QPushButton:hover { background-color: #689F38; }
|
|
46
|
+
""")
|
|
47
|
+
header.addWidget(btn_back)
|
|
48
|
+
|
|
49
|
+
title = QLabel("2D-3D Calibration (LiDAR-Camera)")
|
|
50
|
+
title.setStyleSheet("font-size: 18px; font-weight: bold; color: #FF9800;")
|
|
51
|
+
header.addWidget(title)
|
|
52
|
+
header.addStretch()
|
|
53
|
+
layout.addLayout(header)
|
|
54
|
+
|
|
55
|
+
# Tabs
|
|
56
|
+
self.tabs = QTabWidget()
|
|
57
|
+
self.tabs.setStyleSheet("""
|
|
58
|
+
QTabBar::tab {
|
|
59
|
+
min-width: 150px;
|
|
60
|
+
padding: 8px;
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
}
|
|
63
|
+
QTabBar::tab:selected {
|
|
64
|
+
background-color: #E65100;
|
|
65
|
+
color: white;
|
|
66
|
+
}
|
|
67
|
+
""")
|
|
68
|
+
self.init_tabs()
|
|
69
|
+
layout.addWidget(self.tabs)
|
|
70
|
+
|
|
71
|
+
self.setLayout(layout)
|
|
72
|
+
|
|
73
|
+
def init_tabs(self):
|
|
74
|
+
# 1. Record (Multimodal) - Uses ExtractorWidget
|
|
75
|
+
rec_widget = ExtractorWidget(enable_depth=False, script_name="2d_3d_data_extractor.py")
|
|
76
|
+
rec_widget.back_clicked.connect(self.go_home.emit)
|
|
77
|
+
self.tabs.addTab(rec_widget, "Record (2D-3D)")
|
|
78
|
+
|
|
79
|
+
# 2. Calibration (moved up after Record)
|
|
80
|
+
self.tabs.addTab(wrap_with_back(CalibratorWidget(), self.go_home), "Calibrate")
|
|
81
|
+
|
|
82
|
+
# 3. Validation with Visualization
|
|
83
|
+
self.tabs.addTab(wrap_with_back(ValidatorWidget(), self.go_home), "Validate")
|
|
84
|
+
|
|
85
|
+
# 4. Upload
|
|
86
|
+
self.tabs.addTab(wrap_with_back(UploaderWidget(), self.go_home), "Fleet Upload")
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
2
|
+
QLineEdit, QPushButton, QMessageBox, QGroupBox, QFileDialog)
|
|
3
|
+
from PyQt5.QtCore import QThread, pyqtSignal
|
|
4
|
+
import os
|
|
5
|
+
import json
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
class UploadWorker(QThread):
|
|
9
|
+
finished = pyqtSignal(bool, str)
|
|
10
|
+
|
|
11
|
+
def __init__(self, server_url, file_path, robot_id, score):
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.server_url = server_url
|
|
14
|
+
self.file_path = file_path
|
|
15
|
+
self.robot_id = robot_id
|
|
16
|
+
self.score = score
|
|
17
|
+
|
|
18
|
+
def run(self):
|
|
19
|
+
try:
|
|
20
|
+
url = f"{self.server_url}/api/upload"
|
|
21
|
+
|
|
22
|
+
metadata = {
|
|
23
|
+
'robot_id': self.robot_id,
|
|
24
|
+
'score': self.score
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
with open(self.file_path, 'rb') as f:
|
|
28
|
+
files = {'artifacts': f}
|
|
29
|
+
data = {'metadata': json.dumps(metadata)}
|
|
30
|
+
|
|
31
|
+
response = requests.post(url, files=files, data=data)
|
|
32
|
+
|
|
33
|
+
if response.status_code in [200, 201]:
|
|
34
|
+
self.finished.emit(True, "Upload Successful")
|
|
35
|
+
else:
|
|
36
|
+
self.finished.emit(False, f"Server Error: {response.status_code}")
|
|
37
|
+
|
|
38
|
+
except Exception as e:
|
|
39
|
+
self.finished.emit(False, str(e))
|
|
40
|
+
|
|
41
|
+
class UploaderWidget(QWidget):
|
|
42
|
+
def __init__(self):
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.initUI()
|
|
45
|
+
|
|
46
|
+
def initUI(self):
|
|
47
|
+
layout = QVBoxLayout()
|
|
48
|
+
|
|
49
|
+
group = QGroupBox("Upload to Fleet Server")
|
|
50
|
+
form = QVBoxLayout()
|
|
51
|
+
|
|
52
|
+
# Server URL
|
|
53
|
+
url_layout = QHBoxLayout()
|
|
54
|
+
self.url_input = QLineEdit("http://localhost:5000")
|
|
55
|
+
url_layout.addWidget(QLabel("Server URL:"))
|
|
56
|
+
url_layout.addWidget(self.url_input)
|
|
57
|
+
form.addLayout(url_layout)
|
|
58
|
+
|
|
59
|
+
# Robot ID
|
|
60
|
+
rid_layout = QHBoxLayout()
|
|
61
|
+
self.rid_input = QLineEdit("AMR_001")
|
|
62
|
+
rid_layout.addWidget(QLabel("Robot ID:"))
|
|
63
|
+
rid_layout.addWidget(self.rid_input)
|
|
64
|
+
form.addLayout(rid_layout)
|
|
65
|
+
|
|
66
|
+
# File selector with browse button
|
|
67
|
+
file_layout = QHBoxLayout()
|
|
68
|
+
self.file_input = QLineEdit("calib_result.yaml")
|
|
69
|
+
self.btn_browse = QPushButton("Browse...")
|
|
70
|
+
self.btn_browse.clicked.connect(self.browse_file)
|
|
71
|
+
self.btn_browse.setFixedWidth(100)
|
|
72
|
+
file_layout.addWidget(QLabel("Result File:"))
|
|
73
|
+
file_layout.addWidget(self.file_input)
|
|
74
|
+
file_layout.addWidget(self.btn_browse)
|
|
75
|
+
form.addLayout(file_layout)
|
|
76
|
+
|
|
77
|
+
# Upload Button
|
|
78
|
+
self.btn_upload = QPushButton("☁️ Upload Result")
|
|
79
|
+
self.btn_upload.clicked.connect(self.start_upload)
|
|
80
|
+
self.btn_upload.setStyleSheet("""
|
|
81
|
+
QPushButton { background-color: #9C27B0; color: white; padding: 10px; }
|
|
82
|
+
QPushButton:hover { background-color: #BA68C8; }
|
|
83
|
+
""")
|
|
84
|
+
form.addWidget(self.btn_upload)
|
|
85
|
+
|
|
86
|
+
group.setLayout(form)
|
|
87
|
+
layout.addWidget(group)
|
|
88
|
+
self.setLayout(layout)
|
|
89
|
+
|
|
90
|
+
def browse_file(self):
|
|
91
|
+
"""Open file dialog to select calibration result file"""
|
|
92
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
93
|
+
self,
|
|
94
|
+
"Select Calibration Result File",
|
|
95
|
+
".", # Start in current directory
|
|
96
|
+
"YAML files (*.yaml *.yml);;All files (*.*)"
|
|
97
|
+
)
|
|
98
|
+
if file_path:
|
|
99
|
+
self.file_input.setText(file_path)
|
|
100
|
+
|
|
101
|
+
def start_upload(self):
|
|
102
|
+
path = self.file_input.text()
|
|
103
|
+
url = self.url_input.text()
|
|
104
|
+
rid = self.rid_input.text()
|
|
105
|
+
|
|
106
|
+
if not os.path.exists(path):
|
|
107
|
+
msg_box = QMessageBox(self)
|
|
108
|
+
msg_box.setIcon(QMessageBox.Warning)
|
|
109
|
+
msg_box.setWindowTitle("Error")
|
|
110
|
+
msg_box.setText("Result file not found")
|
|
111
|
+
msg_box.setStyleSheet("QMessageBox { background-color: #404040; color: white; }")
|
|
112
|
+
msg_box.exec_()
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
# Parse score from yaml if possible, else 0
|
|
116
|
+
score = 0.0
|
|
117
|
+
try:
|
|
118
|
+
# simple parse
|
|
119
|
+
with open(path, 'r') as f:
|
|
120
|
+
for line in f:
|
|
121
|
+
if "avg_point_to_plane_rmse_m" in line:
|
|
122
|
+
score = float(line.split(':')[1].strip())
|
|
123
|
+
break
|
|
124
|
+
except:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
self.btn_upload.setEnabled(False)
|
|
128
|
+
self.btn_upload.setText("Uploading...")
|
|
129
|
+
|
|
130
|
+
self.worker = UploadWorker(url, path, rid, score)
|
|
131
|
+
self.worker.finished.connect(self.on_finished)
|
|
132
|
+
self.worker.start()
|
|
133
|
+
|
|
134
|
+
def on_finished(self, success, msg):
|
|
135
|
+
self.btn_upload.setEnabled(True)
|
|
136
|
+
self.btn_upload.setText("☁️ Upload Result")
|
|
137
|
+
|
|
138
|
+
if success:
|
|
139
|
+
msg_box = QMessageBox(self)
|
|
140
|
+
msg_box.setIcon(QMessageBox.Information)
|
|
141
|
+
msg_box.setWindowTitle("Success")
|
|
142
|
+
msg_box.setText(msg)
|
|
143
|
+
msg_box.setStyleSheet("QMessageBox { background-color: #404040; color: white; }")
|
|
144
|
+
msg_box.exec_()
|
|
145
|
+
else:
|
|
146
|
+
msg_box = QMessageBox(self)
|
|
147
|
+
msg_box.setIcon(QMessageBox.Critical)
|
|
148
|
+
msg_box.setWindowTitle("Error")
|
|
149
|
+
msg_box.setText(msg)
|
|
150
|
+
msg_box.setStyleSheet("QMessageBox { background-color: #404040; color: white; }")
|
|
151
|
+
msg_box.exec_()
|